Table of Contents |
---|
Fixture Conventions
...
Introduction
Even though one of the objectives of
is to create a common language between the business experts and the development team, there will always be a certain degree of difference between the natural language and the programing language. Hence comes the reason for having fixtures. Fixtures are the glue between the business expert examples and the software being developed. When running the table, uses a fixture to mediate between the example expressed in the table and the system under test.Collaboration demands Compromise
The goal of the fixture, is to translate from one language to the other so neither has to compromise their clarity or their design to match the other. The fixture is the compromise. A fixture is any class. It does not have to extend or implement any base class/interface.
The fixture name
The fixture name is found right next to the interpreter specification.
...
Since a fixture is a Java class, when GreenPepper executes the example, it will try to match the fixture name with a class name.
What about packages?
Usually, Java classes are found inside a package and we can explicitly load a class via the package name.
An explicitly imported fixture | ||||
---|---|---|---|---|
|
Readability
The problem with packages and namespace and with the classes naming convention, camel casing and no spaces, is that they makes the example less readable for the business expert.
To help readability, we have the following options:
Import tables
Import tables are special tables at the beginning of the document.
...
GreenPepper will search for the fixture in each of these packages until it finds a matching class.
Humanized name
Event without the package, programmatic naming conventions are not the most readable form for the name of the example. This can be arranged by following the camel casing conventions.
...
Note |
---|
When using the humanized version of fixture naming, you must use Developer Guide (Under Construction)Final. Explicit package won't work ex: |list of | com.xyz.stuff.the fixture name| |
The fixture suffix
If you wish to clearly distinguish between your domain classes and the classes that serve as fixture, you can add the suffix Fixture at the end of the fixture classes name.
...
Suffix example | |||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
and
will both match
|
Constructor
A fixture can receive parameters during it's construction. You must have a public constructor that matches the numbers of parameters.
...
An example with two parameters | |||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
Rule validation (Rule For)
Definition
Definition |
---|
The RuleForInterpreter is used to express concrete and measurable business rules.
|
Specific keywords for expected values
offers a list of useful keywords to support the Business Expert.
Empty cells | When a test cell is left blank, | only shows the returned value
---|---|
error | When you expect an error, specify it in the cell to test that particular behavior |
Coloring
will visually show the test result by coloring each testing cell:
...
rule for | Calculcator | |||
---|---|---|---|---|
x | y | sum? | product() | quotient? |
10 | 0 | 10 | 0 | 0 java.lang.ArithmeticException: / by zero |
6 | 3 | 9 | Expected: -5 Received: 18 | 2 |
Writing fixtures for Rule tables
As we've seen in the Rule For Definition, a table of rules is used to express business rules of the application under development.
...
This page shows the fixture code that supports the examples introduced in the Writing a Rule For Specification documentation.
Fixture for Calculator
Consider the first example of business rule table described in Table of Rules documentation, shown again below.
...
The fixture code to support this example in Java is the class Calculator shown below.
Show me the code
Code Block | ||||
---|---|---|---|---|
| ||||
public class Division { public double dividend; public double divisor; public double quotient() { return dividend / divisor; } } |
That class follows the general rules of fixtures described in the Developer Guide (Under Construction)Final. It exposes the instance variable dividend to map with the given column dividend. The given column divisor corresponds to the public instance variable divisor. The expected column quotient? is mapped to the public instance method quotient().
Info |
---|
This is a very simple example in which the fixture object does not call on the system under development, but performs the calculation. The quotient() method uses the instance variables dividend and divisor to perform the calculation by itself. |
How is the table processed?
When it runs this table,
reads the first row to select the interpreter and fixture. It then reads the second to know what are the given and expected columns. Finally it starts testing from the third row down to the end of the table....
On the other hand, on the last row the comparison will fail.
will mark the expected cell wrong. That cell will be colored red and will display a message including the expected value and the actual value.How are the types of the values handled?
The instance variables dividend and divisor are of type double, so the given values in the example table needs to be double as well. For the fourth row,
will automatically convert the given values 7 and 2 to doubles before assigning them to the instance variables.The return value of the quotient method is also a double, which means that the values provided in the quotient? expected column must be doubles as well. When
compares the value returned by the fixture with the value provided as an expectation, it will first convert the expected value to the type of the actual returned value. In the fifth row, it will convert the value 6 to a double and then do the comparison.A More Realistic Example
The Calculator example is very simple. In a real world example, the fixture code would not perform any real work but would instead delegate to the application under development. In other words, if this was a real application the fixture would not carry out the division operation but call on the system under development. The general rule is to keep the fixture as thin as possible to be merely a mediator between the example table and the application code.
...
This example uses the InsurancePremiumFeeCalculation fixture class. The two given columns, which are sale price and down payment, are mapped respectively to the instance variables salePrice and downPayment. The two expected columns, called premium fee? and financed amount?, correspond respectively to the methods premiumFee() and financedAmount().
How are spaces between words handled?
You have noticed that you can separate words in the example table with spaces.
will convert the sequence a space-separated words to a valid Java identifier by removing the spaces and capitalizing the first letter of every word except the first one. This is called camel casing.- For class names, uses upper camel casing with the first letter of the identifier capitalized as well. In our example, the label insurance premium fee calculation is converted to the class name InsurancePremiumFeeCalculation.
- For method or instance variable names, uses lower camel casing where the first letter of the identifier is left in lowercase.
Show me the code
The supporting code is here:
...
Code Block | ||||
---|---|---|---|---|
| ||||
public class InsuranceFee { private final Money salePrice; public InsuranceFee(Money salePrice) { this.salePrice = salePrice; } public Money forDownPayment(Money downPayment) { return downPayment.greaterThan(salePrice.times(Ratio.percent(25))) ? Money.zero() : financedAmount( downPayment ).times( Ratio.of(25, 1000) ); } private Money financedAmount(Money downPayment) { return salePrice.minus( downPayment ); } } |
What about the empty column?
The financed amount? expected value is left empty to tell
that this is an information column. We don't want to perform any test in that column, but rather show the value to help our understanding of the calculation. The calculated values will be shown when the example is run and marked with the ignored annotation, making them appear gray.How are application value types handled?
We've seen that
will automatically convert the given values in the table to the type of the corresponding instance variable....
Code Block | ||||
---|---|---|---|---|
| ||||
public class Money { private final BigDecimal dollars; public Money(BigDecimal dollars) { this.dollars = dollars; } public static Money parse(String text) { return new Money( new BigDecimal( normalize( text ) ) ); } private static String normalize(String text) { return text.replaceAll( "\\$", "").replaceAll( ",", "").replaceAll( "\\s", ""); } // ... public boolean equals(Object other) { if (other instanceof Money) { Money that = (Money) other; return this.dollars.compareTo( that.dollars ) == 0; } else { return false; } } public int hashCode() { return dollars.hashCode(); } public String toString() { return "$" + dollars; } // ... } |
Row actions in ruleFor
There are 3 annotations(attributes) that can be used in the fixture with a RuleForInterpreter.
- BeforeRow
- AfterRow
- BeforeFirstExpectation
BeforeRow
A method annotated with BeforeRow will be call at the beginning of each new row so you can prepare your fixture.
AfterRow
A method annotated with AfterRow will be call at the end of each row so you can reset your fixture.
BeforeFirstExpectation
The method annotated with BeforeFirstExpectation will be called on each row just before the first ExpectedColumn is encountered so you can prepare the return values for this row.
...
The three annotations are optional, you can use none, some or all of them in your fixture depending on your needs.
Wrap Up
When
runs a rule table example, it creates a fixture class from the name indicated in the second cell of the first row. That class is used to mediate between the development table and the application code when checking the example against the system under development....
- given values, which are mapped to public instance variables in the fixture class
- expected values, which are checked against values returned by the corresponding public instance methods
Headers
For a given column header cell,
:...
- It figures out the public instance method to use using camel casing rules.
- If there is no such method or if it's not publicly accessible, it marks the cell with an error annotation, causing the cell to appear yellow and to display a stack trace of the error. That column will be ignored entirely.
Columns
will then run all remaining rows one at a time, going through all cells in the row from left to right.
...
- It converts the text entered in the cell to the return type of the instance method. To do that it uses either built-in converters (for basic Java types) or delegates to a method called parse (for domain types).
- It compares the actual value to the converted value based on the definition of the equality for the type.
- If the values match, it marks the cell with a right annotation causing it to appear green.
- If the values do not match, it marks the cell with a wrong annotation so that it appears red and displays the expected and actual values.
- However, if type conversion fails or if the method throws an exception the cell is reported as an error.
List Validation (List of, Set of, Superset of, Subset of)
Definition
DEFINITION |
---|
The collection interpreters are used to express any kind of groups, lists or sets of values. When a collection interpreter is executed, compares the list of values expected with the list of values returned by the system under development. The test result depends on the specific collection interpreter selected.
|
Specific keywords
none
Coloring
will visually indicate the test result by coloring the rows:
...
Particular behavior for the superset of interpreters:
Since the SupersetOfInterpreter accepts that the specification may contain rows that are not contained in the SUD, will not color the rows contained in the specification but not returned by the SUD.
Writing fixtures for List tables
Writing fixtures for List of Value
As we've seen in the Collection Interpreters, the collection interpreters are used to express a collection of data to be compared with the system under development.
...
This page shows the fixture code that supports the examples introduced in the definition.
Using the CollectionInterpreter alone
Consider the example of collection of values table described in definition, shown again below.
...
The fixture code to support this example in Java is the class CanadaProvinceCodesFixture shown below.
The query method
The
method called to get the list of data from the fixture is query() in Java....
but the method specifies still have to be parameter-less and return a Collection implementation or an array.
Show me the code
Code Block | ||||
---|---|---|---|---|
| ||||
public class CanadaProvinceCodesFixture { public Set<Province> query() { return Country.canada().provinces(); } } |
Code Block | ||||
---|---|---|---|---|
| ||||
public class Country { private String name; private Set<Province> provinces = new TreeSet<Province>(); private Country(String name) { this.name = name; } public static Country canada() { Country canada = new Country("CANADA"); canada.provinces.add(new Province("ALBERTA","AB")); canada.provinces.add(new Province("BRITISH COLUMBIA","BC")); canada.provinces.add(new Province("MANITOBA","MB")); canada.provinces.add(new Province("NEW BRUNSWICK","NB")); canada.provinces.add(new Province("NEWFOUNDLAND and LABRADOR","NL")); canada.provinces.add(new Province("NOVA SCOTIA","NS")); canada.provinces.add(new Province("NUNAVUT","NU")); canada.provinces.add(new Province("ONTARIO","ON")); canada.provinces.add(new Province("PRINCE EDWARD ISLAND","PE")); canada.provinces.add(new Province("QUEBEC","QC")); canada.provinces.add(new Province("SASKATCHEWAN","SK")); canada.provinces.add(new Province("YUKON ","YT")); return canada; } public Set<Province> provinces() { return provinces; } } public class Province implements Comparable { public String name; public String code; public Province(String name, String code) { this.name = name; this.code = code; } public int compareTo(Object o) { return name.compareTo(((Province)o).name); } } |
How is the table processed?
When it runs this table,
reads the first row to select the interpreter and fixture. It then reads the second to know what are the attribute columns. Finally it starts testing from the third row down to the end of the table....
The last row is not part the set of Province returned by the system under development. In that particular case,
will insert the keyword missing and color the row in red.Using a CollectionInterpreter joined with the DoWithInterpreter
In this particular case, there is only one fixture combining the methods of the CollectionInterpreter and the methods of the DoWithInterpreter. So, in the CollectionInterpreter table, the second cell of the first row says which method to call from the DoWithInterpreter fixture.
...
list of | Phone book entries |
FirstName | LastName |
Fred | Flintstone |
Betty | Rubble |
Great | Gazoo |
Wilma | Flintstone |
Show me the code
Code Block | ||||
---|---|---|---|---|
| ||||
public class PhoneBookFixture { private PhoneBook phoneBook = new PhoneBook(); public void insertWithNumber(String firstName, String lastName, String number) { phoneBook.insert(new PhoneBookEntry(firstName, lastName, number)); } public List<PhoneBookEntry> query() { return phoneBook.entries(); } } |
Code Block | ||||
---|---|---|---|---|
| ||||
public class PhoneBook { private List<PhoneBookEntry> entries = new ArrayList<PhoneBookEntry>(); public void insert(PhoneBookEntry entry) { entries.add(entry); } public List<PhoneBookEntry> entries() { return entries; } } public class PhoneBookEntry { public String firstName; public String lastName; public String number; public PhoneBookEntry(String firstName, String lastName, String number) { this.firstName = firstName; this.lastName = lastName; this.number = number; } } |
Writing fixtures for derived List of Value (SetOf, SubsetOf, SupersetOf)
The fixture writing and results annotations are exactly similar in all points.
Of course the behavior of the test will vary see Collection Interpreters definition for detail.
Workflow validation (Do With)
Definition
DEFINITION |
---|
The DoWithInterpreter is used to express interactions with the system under development that must be performed in a particular order. This form of specification provides information about the business flow. When a sequence of action is executed, confirms that each action has successfully been performed.
|
Specific Keywords
offers a list of useful keywords to support the Business Expert. Those keywords are placed at the beginning of an action row.
Accept | Confirm that the action as been executed by the system under development. |
---|---|
Check | Verify the specified expected value with the value returned by the system under development |
Reject | The action should not be performed by the system under development (expected errors). |
Display | Print the value returned by the system under development. |
Coloring
will visually show the test result by coloring each testing cell:
...
Standard form (without keyword) | Only the Action description will be colored. |
---|---|
Accept | Only the cell containing the keyword Accept will be colored. |
Check | The cell containing the expected value will be colored. In case of a failure, | will show the expected and the returned values.
Reject | Only the cell containing the keyword Reject will be colored. |
Display | A new cell at the end of the row will be colored containing the returned value. |
Writing fixtures for Do With tables
As we've seen in the Do With definition, a sequence of tables is used to express a business flow in the application under development.
...
This page shows the fixture code that supports the examples introduced in the Writing a Do With specification.
Fixture for Bank
Consider the first example of business flow described in Writing a Do With specification, shown again below.
...
The fixture code to support this example in Java is the class BankFixture shown below.
Show me the code
Code Block | ||||
---|---|---|---|---|
| ||||
public class BankFixture { private Bank bank; public BankFixture() { bank = new Bank(); } public boolean openCheckingAccountUnderTheNameOf(String number, String firstName, String lastName) { return bank.openCheckingAccount(number, new Owner(firstName, lastName)) != null; } public Money thatBalanceOfAccountIs(String accountNumber) throws Exception { BankAccount account = bank.getAccount(accountNumber); return account.getBalance(); } } |
That class follows the general rules of fixtures described in Developer Guide (Under Construction)Final. It provides public instance methods openCheckingAccount and thatBalanceOfAccount to map respectively to the actions open checking account and that balance of account.
...
Code Block | ||||
---|---|---|---|---|
| ||||
public class Bank { private final HashMap<String, BankAccount> accounts; public Bank() { accounts = new HashMap<String, BankAccount>(); } public boolean hasAccount(String accountNumber) { return accounts.containsKey(accountNumber); } public BankAccount getAccount(String accountNumber) throws NoSuchAccountException { if (!hasAccount(accountNumber) throw new NoSuchAccountException(accountNumber); return accounts.get(accountNumber); } public CheckingAccount openCheckingAccount(String number, Owner owner) { if (hasAccount(number)) return null; CheckingAccount account = new CheckingAccount(number, owner); accounts.put(number, account); return account; } } |
How is the example interpreted?
When it runs this example,
reads the first table to decide on the interpreter and fixture to use and start testing from the second table, which is the first test table....
- It calls the method thatBalanceOfAccountIs() with the parameter 12345-67890 to get the value calculated by the system under test
- This is a check action, so it reads the value $0.00 from the last cell of the row and compares it to the value returned by the fixture. Since the values are equal, it annotates the last cell as right, which results in the cell being colored green.
What happens for other return types?
Default Rows
Depending on the value returned by the system under test, default actions will annotate keyword cells following these rules:
- If the value is true, it annotates keyword cells right, making them appear green.
- If the value is false - indicating a failure - it annotates keyword cells wrong, making them appear red.
- If the action throws an exception, it annotates the first keyword as an exception, making it appear yellow and display a stack trace of the error.
- If the action returns another value or nothing, it ignores the result.
Check Rows
Depending on the value returned by the system under test, check actions will annotate the row following these rules:
- If the returned value matches the expected value, it annotates the last cell right, making it appear green.
- If the action returns nothing or a value that does not match the expected value - indicating a failure - it annotates the last cell wrong, making it appear red.
- If the action throws an exception, it annotates the first keyword as an exception, making it appear yellow and display a stack trace of the error.
Building on the Bank example
The second example in Writing a Do With Specification, shown again below, presents a more complete business flow using the bank fixture.
...
In the last row of the last table, the accept special keyword is used to indicate that we expect the last call to succeed. Accept is the opposite of reject. That is, the last withdraw should not return false nor throw an exception.
Reject Rows
Depending on the value returned by the system under test, reject actions will annotate the row following these rules:
- If the action returns false or throws an exception - indicating a success - it annotates the reject keyword right, making it appear green.
- If the returned value is anything else or nothing - indicating a failure - , it annotates the reject keyword wrong, making it appear red.
Accept Rows
Depending on the value returned by the system under test, accept actions will annotate the row following these rules:
- If the returned value is anything except false or an exception - indicating a success -, it annotates the accept keyword right, making it appear green.
- If the action returns false or throws an exception - indicating a failure - it annotates the accept keyword wrong, making it appear red.
Show me the code
The supporting code is here:
Code Block | ||||
---|---|---|---|---|
| ||||
public class BankFixture { private Bank bank; public BankFixture() { bank = new Bank(); } public boolean openCheckingAccountUnderTheNameOf(String number, String firstName, String lastName) { return bank.openCheckingAccount(number, new Owner(firstName, lastName)) != null; } public Money thatBalanceOfAccountIs(String accountNumber) throws Exception { BankAccount account = bank.getAccount(accountNumber); return account.getBalance(); } public void depositInAccount(Money amount, String accountNumber) throws Exception { bank.deposit(amount, accountNumber); } public boolean withdrawFromAccount(Money amount, String accountNumber) throws Exception { return withdrawFromAccountUsing( amount, accountNumber, WithdrawType.ATM ); } public boolean withdrawFromAccountUsing(Money amount, String accountNumber, WithdrawType withdrawType) throws Exception { try { bank.withdraw(amount, accountNumber, withdrawType); } catch (Exception e) { return false; } return true; } public Collection getOpenedAccounts() { return bank.getAccounts(); } } |
Combining with other types of rules
An interesting characteristic of the DoWithInterpreter is the ability to delegate processing of part of the table to another interpreter.
...
set of | opened accounts | |
number | type | owner name |
12345-67890 | checking | Spongebob Squarepants |
54321-09876 | savings | Patrick Star |
end |
Interpret Rows
Depending on the value returned by the system under test, interpret actions will annotate the row following these rules:
- If the returned value is an object, it uses the interpreter specified in the first cell on that object to interpret the remainder of the table. The row is not annotated.
- If the action returns nothing, it uses the interpreter without any fixture to interpret the remainder of the table. The row is not annotated.
- If the action throws an exception - indicating a failure - it annotates the first action keyword as exception, making appear yellow and display a stack trace of the error. The remainder of the table is not interpreted.
Workflow validation (Scenario Fixture)
Definition
Definition | |||||||
---|---|---|---|---|---|---|---|
The ScenarioInterpreter is used to express interactions with the system under development that must be performed in a particular order. This form of specification provides information about the business flow. When a sequence of action is executed, confirms that each action has successfully been performed.
|
Coloring
will visually show the test result by coloring a complete row or words inside the row:
...
Panel | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
When the action has been executed successfully, will display the returned value in gray. |
Writing fixtures for Scenario tables
As we've seen in the Scenario definition, a sequence of tables is used to express a business flow in the application under development.
...
This page shows the fixture code that supports the examples introduced in the Writing a Scenario specification.
Fixture for Bank
Consider the first example of business flow described in Writing a Scenario specification, shown again below.
...
The second table indicates to perform the action of opening a checking account 12345-67890 under the name of Spongebob Squarepants on the system under development. That action will result in a call to a method from the Bank fixture. The method will be found using Regular Expression assigned to an annotation.
Annotation & Regular Expression
To resolve the method to call, the ScenarioInterpreter will look at all fixture methods annotated with the following annotations and will try to match the action content to the regular expression on it. Parameters will be captured by the regular expression itself (using capture group).
@Given
Panel | ||||
---|---|---|---|---|
| ||||
@Given("[regular expression]") |
A Given annotation will be use when you need to put the system in a know state before a user interact with it. A good example will be to prepare the data in the system to be available for the next action calls. In our Bank example, this will represent the opening of the checking account action.
@When
Panel | ||||
---|---|---|---|---|
| ||||
@When("[regular expression]") |
A When annotation will be use for transiting the system to another state. A good example will be to interact with the system. In our Bank example this will represent deposit or withdraw actions.
@Then
Panel | ||||
---|---|---|---|---|
| ||||
@Then("[regular expression]") |
A Then annotation will be use to verify the result of interactions on the system. A good example will be to check if an event has been raised. In our Bank example, this will represent the verification of the balance account.
@Check
Panel | ||||
---|---|---|---|---|
| ||||
@Check("[regular expression]") |
A Check annotation will be use to verify the result of the action (boolean result equality). A good example will be to check if we can proceed with an action. In our Bank example, this will represent the verification whenever we can withdraw a certain amount.
@Display
Panel | ||||
---|---|---|---|---|
| ||||
@Display("[regular expression]") |
...
The fixture code to support this example in Java is the class BankFixture shown below.
Show me the code
Code Block | ||||
---|---|---|---|---|
| ||||
public class BankFixture { private Bank bank; public BankFixture() { bank = new Bank(); } @Given("open (\\w+) account (\\d{5}\\-\\d{5}) under the name of ([\\w|\\s]*)") public void openAccount(String type, String number, Owner owner) { if ("checking".equals( type )) { bank.openCheckingAccount( number, owner ); } else if ("savings".equals( type )) { bank.openSavingsAccount( number, owner ); } } @Then("verify that balance of account (\\d{5}\\-\\d{5}) is (\\$\\d+\\.\\d\\d)") public void theBalanceOfAccount(String number, Expectation expectedBalance) throws NoSuchAccountException { Money actualBalance = bank.getAccount( number ).getBalance(); expectedBalance.setActual( actualBalance ); } } |
That class follows the general rules of fixtures described in Developer Guide (Under Construction)Final.
Info |
---|
The fixture does not much except from delegating the processing to the application code, in this case the Bank class. |
Code Block | ||||
---|---|---|---|---|
| ||||
public class Bank { private final HashMap<String, BankAccount> accounts; public Bank() { accounts = new HashMap<String, BankAccount>(); } public boolean hasAccount(String accountNumber) { return accounts.containsKey(accountNumber); } public BankAccount getAccount(String accountNumber) throws NoSuchAccountException { if (!hasAccount(accountNumber) throw new NoSuchAccountException(accountNumber); return accounts.get(accountNumber); } public CheckingAccount openCheckingAccount(String number, Owner owner) { if (hasAccount(number)) return null; CheckingAccount account = new CheckingAccount(number, owner); accounts.put(number, account); return account; } } |
How is the example interpreted?
When it runs this example,
reads the first table to decide on the interpreter and fixture to use and start testing from the second table, which is the first test table....
- Find a method that match the action content using regular expression defined on annotated method
- It calls the method theBalanceOfAccount() with the parameter 12345-67890 to get the value calculated by the system under test and the Expectation object instance
- This is a Then annotation. The method will set the actual value on the Expectation object instance. GreenPepper will then verify the Expectation object to see if it matches the value $0.00. If the values are equal, it annotates the value $0.00 as right (green) or wrong (red) if not
Building on the Bank example
The second example in Writing a Scenario specification, shown again below, presents a more complete business flow using the bank fixture.
...
The fourth and last example table contains additional multiple rows. In a sequence of actions, all of the rows in a table are executed, so several actions can be grouped in a table if that helps improve clarity.
Show me the code
Code Block | ||||
---|---|---|---|---|
| ||||
public class BankFixture { private Bank bank; public BankFixture() { bank = new Bank(); } public BankFixture() { this.bank = new Bank(); } @Given("open (\\w+) account (\\d{5}\\-\\d{5}) under the name of ([\\w|\\s]*)") public void openAccount(String type, String number, Owner owner) { if ("checking".equals( type )) { bank.openCheckingAccount( number, owner ); } else if ("savings".equals( type )) { bank.openSavingsAccount( number, owner ); } } @Then("verify that balance of account (\\d{5}\\-\\d{5}) is (\\$\\d+\\.\\d\\d)") public void theBalanceOfAccount(String number, Expectation expectedBalance) throws NoSuchAccountException { Money actualBalance = bank.getAccount( number ).getBalance(); expectedBalance.setActual( actualBalance ); } @When("deposit (\\$\\d+\\.\\d\\d) in account (\\d{5}\\-\\d{5})") public void deposit(Money amount, String number) throws Exception { bank.deposit( amount, number ); } @When("withdraw (\\$\\d+\\.\\d\\d) from account (\\d{5}\\-\\d{5})") public void withdraw(Money amount, String number) throws Exception { bank.withdraw( amount, number, WithdrawType.ATM ); } @Check("can't withdraw (\\$\\d+\\.\\d\\d) from account (\\d{5}\\-\\d{5})") public boolean cannotWithdraw(Money amount, String number) { try { bank.withdraw( amount, number, WithdrawType.ATM ); return false; } catch (Exception e) { return true; } } @Check("can withdraw (\\$\\d+\\.\\d\\d) from account (\\d{5}\\-\\d{5})") public boolean canWithdraw(Money amount, String number) { try { bank.withdraw( amount, number, WithdrawType.ATM ); return true; } catch (Exception e) { return false; } } public Collection<BankAccount> getOpenedAccounts() { return bank.getAccounts(); } } |
Combining with other types of rules
An interesting characteristic of the ScenarioInterpreter is the ability to delegate processing of part of the table to another interpreter.
...
set of | opened accounts | |
number | type | owner name |
12345-67890 | checking | Spongebob Squarepants |
54321-09876 | savings | Patrick Star |
end |
Live example (will there be one?)
See a live example here(NYI).
Context definition (Setup)
Definition
Definition |
---|
The SetUpInterpreter is used to simplify the creation of a particular state for the system under development. Once the state is created, we can focus on the business process to test. When a setup table is executed, enter data in the system under development to create the desired state.
|
Coloring
will visually show the test result by coloring each testing cell:
...
Panel | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
If the insertion has failed because a specified value generates an error, |
Writing fixtures for Setup tables
As we've seen in Setup Definition, a table of rules is used to simplify the creation of a particular state for the system under development. Once the state is created, we can focus on the business process to test.
This page shows the fixture code that supports the examples introduced in the Writing a Setup specification.
Fixture to create bank customers
Consider the example of setup table described in Writing a Setup specification:
...
The fixture code to support this example in Java is the class AGroupOfCustomersFixture shown below.
Show me the code
Code Block | ||||
---|---|---|---|---|
| ||||
public class AGroupOfCustomersFixture { public AccountType type; public String number, firstName, lastName; public Money balance; public static Bank bank; public AGroupOfCustomersFixture() { bank=new Bank(); } @EnterRow public void setupAccount() { if(AccountType.SAVINGS == type) bank.openSavingsAccount(number, new Owner(firstName, lastName)).deposit(balance); else if(AccountType.CHECKING == type) bank.openCheckingAccount(number, new Owner(firstName, lastName)).deposit(balance); } } |
How is the table processed?
When it runs this table,
reads the first row to select the interpreter and fixture. It then reads the second to know what are the attribute columns. Finally it starts creation of entries from the third row down to the end of the table.For the third row
It assigns the data 11111-11111 to number, checking to type, Fred to first name, Flintstone to last name and $250.00 to balance.
It then calls the method enterRow() of the fixture AGroupOfCustomersFixture to open a bank account with the given datas.
Advanced
Defining a custom system under development
Why ?
The system under development is a bridge between your Fixtures and the system your testing.
If you want to change the way is finding/instancing your fixtures, or if you need to hook the document execution, then you can define a Custom System Under Development.
...
- Server system under development configuration(link to-do)
- Command line system under development configuration
Using custom types
All the examples in the documents are in strings, but fixtures want to process and return other data types.
...
Lets look at how
will match these.Converters
Converters are class implementing the interface com.greenpepper.converter.TypeConverter in java.
...
The ArrayConverter calls recursively the other converters depending on the component type the array holds.
Adding and removing new type converters
Adding
The com.greenpepper.GreenPepper class provides a method to add your own type converter
...
The converters are always checked in a LIFO manner. If two converters can process a data type the last one that has been registered will be used. That way, you can provide your own converters in place of the standard
converters.Removing
There are two methods for unregistering added converters, In case you want to use two converters for the same data type during testing.
...
These can be called from a specification or within the custom system under development.
Self conversion
Instead of registering a TypeConverter, you can use self converting types.
...
Code Block | ||
---|---|---|
| ||
public static String toString(T value) |
Rules of conversion
From example to fixture
1 First
2 If not, look for a registered TypeConverter that can handles the type.
3 An UnsupportedOperationException will be thrown
From fixture return value to String
1 First
2 If not, look for a registered TypeConverter that can handles the type.
3 Use the toString() or ToString() method on the data itself.
Customizing GreenPepper fixture resolution
Prerequisite
To change fixtures resolutions you need to define a custom system under development.
Changing how is finding your Fixtures
This could be useful when you are using for example an IOC or just want to add locations (packages in java) for
to resolve your fixtures.Only for specifying location to resolve fixtures (packages in java)
Code Block | ||
---|---|---|
| ||
public static class CustomSystemUnderDevelopment extends DefaultSystemUnderDevelopment { public CustomSystemUnderDevelopment( String... params ) { super.addImport("com.mycompany.fixtures"); super.addImport("com.mycompany.specials.fixtures"); } } |
By this custom system under development you tell
to look in "com.mycompany.fixtures" and "com.mycompany.specials.fixtures" to resolve fixtures in specifications that your are running.Hooking document execution
To hook a document execution, you need to define a custom system under development.
...
Code Block | ||
---|---|---|
| ||
public static class CustomSystemUnderDevelopment extends DefaultSystemUnderDevelopment { public CustomSystemUnderDevelopment( String... params ) { } public void onStartDocument(Document document) { //this method is called before GreenPepper execute a document } public void onEndDocument(Document document) { //this method is called after GreenPepper has executed the document } } |
Execute specifications programmatically
uses two classes to execute specifications programmatically. With this you can include GreenPepper-Tests in the Unittests of your system under develpment.
...
Code Block | ||
---|---|---|
| ||
// ... new SpecificationRunnerExecutor(runner).execute(); SpecificationRunnerExecutor executor = new SpecificationRunnerExecutor(runner).locale(new Locale("")); // A specification runner does not have a output file. if(runner instanceof DocumentRunner) executor.outputFile(outputFile); executor.execute("ACalculatorSample.html"); // ... |
How to run a specification (suite) using the command line?
Requirements
- Installed java runtime
- Your compiled fixture classes/jar (System under Test)
- Your compiled classes/jar (System under Development)
- Your specifications files
- greenpepper-cli-plugin-x.x.x.jar
- A command line tool.
...
Code Block | ||
---|---|---|
| ||
java -cp greenpepper-core-X.X.X-all.jar;path/to/systemundertest/classes;path/to/systemunderdevelopment/classes; com.greenpepper.runner.Main -s /path/to/myspecs /path/to/outputresults |
Configuration
GreenPepper Command line Configuration
How to deal with static fixture fields and the programmatically execution?
You will run into trouble if your tests are using static fields, since these are stored together with the class in the used class loader of the SpecificationBuilder. The error occurs if you are going to use the same class loader for different runners (after the first run all static fields are filled). Therefore a new option was implemented to set a custom fixture class loader. If no one is given the default class loader will be used:
Code Block | ||
---|---|---|
| ||
// ... ClassLoader myFirstRunFixtureClassLoader = ClassUtils.toClassLoaderWithNoParent("path/to/my/fixture.jar"); ClassLoader mySecondRunFixtureClassLoader = ClassUtils.toClassLoaderWithNoParent("path/to/my/fixture.jar"); SpecificationRunnerBuilder builder = new SpecificationRunnerBuilder(specification.getRepository().asCmdLineOption()) .classLoader(joinClassLoader) .specificationRunnerClass(runnerClass) .sections(sectionsArray) .report(reportClass.getName()) .systemUnderDevelopment(systemUnderTest.fixtureFactoryCmdLineOption()) .withMonitor(recorderMonitor) .withMonitor(loggingMonitor) .outputDirectory(outputFile.getParentFile()); builder.fixtureClassLoader(myFirstRunFixtureClassLoader).execute(); builder.fixtureClassLoader(mySecondRunFixtureClassLoader).execute(); // ... |
How to add a custom specification parser?
Create a DocumentBuilder
The best and fastest solution would be to create a new com.greenpepper.repository.DocumentBuilder. This builder should translate your custom markup (e.g. Markdown) into HTML and in fact return a new Document build by the com.greenpepper.html.HtmlDocumentBuilder. In fact: build a HtmlDocumentBuilder wrapper.
...
Code Block | ||
---|---|---|
| ||
// ... public class MarkdownDocumentBuilder implements DocumentBuilder { @Override public Document build(Reader reader) throws IOException { String fileContent = IOUtil.readContent( reader ); String htmlContent = new MarkdownProcessor().markdown(markup); Reader stringReader = new StringReader(htmlContent); return HtmlDocumentBuilder.tablesAndLists().build( reader ); } } |
Alternative
Another option would be to let the Markup-/downDocumentBuilder only convert to HTML, therefore preventing possible confusion with library names (e.g. HTMLDocumentBuilder).
...
Code Block | ||||
---|---|---|---|---|
| ||||
// ... public class WikimarkupDocumentBuilder { StringWriter writer = new StringWriter(); /* MyLyn WikiText HTML-Builder */ HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer, true); MarkupParser markupParser = new MarkupParser(); public File build(File file) throws IOException { Reader reader = new FileReader(file); String html = IOUtils.toString(reader); markupParser.setMarkupLanguage(new ConfluenceLanguage()); markupParser.setBuilder(builder); markupParser.parse(html); String htmlcontent = writer.toString(); File converted = File.createTempFile("wikiconverttemp", null); Files.write(converted.toPath(), htmlcontent.getBytes()); writer.close(); converted.deleteOnExit(); return converted; } // ... |
Extend the existing Repositories
All repositories are defining their supported file types. Therefore we have also to extend the repository implementations which should support our new file type/specification format.
...
Code Block | ||
---|---|---|
| ||
//... public enum FileTypes { HTML("html"), MARKUP("markup"), CONFLUENCE("confluence"), NOTSUPPORTED("nosup"); //... |
How to add aliases for an interpreter (e.g. for i18n purposes)
Aliases can be used to translate interpreter names in specifications. They are case sensitive and can contain special characters (like whitespaces) and umlauts. Whitespaces at the beginning and at the end are always removed.
...
In general there are 3 ways to add an alias for an interpreter:
Programmatically (on runtime)
Code Block | ||
---|---|---|
| ||
GreenPepper.aliasInterpreter("Scenario", ScenarioInterpreter.class); GreenPepper.aliasInterpreter("My szenario", ScenarioInterpreter.class); GreenPepper.aliasInterpreter("My szenario", "com.greenpepper.interpreter.ScenarioInterpreter"); GreenPepper.aliasInterpreter("My wesome scenario!", "com.greenpepper.interpreter.AClass$AInnerClass"); |
Edit the default aliases.properties file
This aliases.properties property file is located in the jar of greenpepper core. Using the sources this file can be found under src/main/resourecs/aliases.properties.
Provide an aliases.properties file
You can place a file named "aliases.properties" next to your greenpepper jar. By default the internal jar properties file is used as fallback.
...
Note |
---|
Aliases from property files will be processed only once during the first run of a specification or if the according class loader of com.greenpepper.GreenPepper is unloaded (garbage collected). |
Usage example
You can use the aliases in any table.
...