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.
simple fixture name | ||||||||||||||
|
|
|
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.
By using an import table we can remove the package from the fixture name thus improving the readability of the example.
an import table | ||
---|---|---|
|
When GreenPepper will search for fixtures in the code, it will look into all the packages specified in the import tables.
Example of fixture name with implicit import | ||||||
---|---|---|---|---|---|---|
GreenPepper will match com.xyz.mystuff.FixtureName. |
You can have more than one package or namespace imported in a document. Just add more lines to the import table.
an import table with multiple imports | ||||
---|---|---|---|---|
|
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.
Use a free form with space separating each word for the fixture name and make the fixture class use camel casing.
GreenPepper will match the words of the fixture name with the camel cased class name.
Camel casing multiple word fixture name |
---|
fixture name |
When using the humanized version of fixture naming, you must use 4. Developer Guide.
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.
When writing the example, you can omit the suffix Fixture from the fixture name. This keeps the example closer to the real domain.
Suffix example | ||||||||
---|---|---|---|---|---|---|---|---|
and
will both match public class BankAccountFixture{ ... } |
Constructor
A fixture can receive parameters during it's construction. You must have a public constructor that matches the numbers of parameters.
When no parameters are specified in the example, the fixture class must have a public constructor without parameters.
Empty constructor example | ||||
---|---|---|---|---|
public class BankAccountFixture { public BankAccountFixture() { ... } } |
An example with two parameters | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
public class PokerTableFixture { public PokerTableFixture(Ammount ante, Ammount maxBet) { ... } } |
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:
When the expected value matches the returned value, the RuleForInterpreter colors the cell as "right" by coloring it green.
If the values don't match, the RuleForInterpreter colors the cell as "wrong" in red.
If the system encounters an execution error, the cell is colored yellow and
provides information about the error.If no expected value is specified in a test, the RuleForInterpreter colors the cell in gray and displays the returned value.
Here is an example of cell coloring:
Input Table:
rule for | Calculator | |||
---|---|---|---|---|
x | y | sum? | product() | quotient? |
10 | 0 | 10 | 0 | 0 |
6 | 3 | 9 | -5 |
Output Table:
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.
A fixture for a table of rules defines how the specific given and expected columns of a rule table are mapped to the system 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.
rule for | Division | |
dividend | divisor | quotient? |
6.0 | 2.0 | 3.0 |
7 | 2 | 3.5 |
18 | 3.0 | 6 |
15 | 2 | 7 |
The first cell of the first row indicates that a RuleForInterpreter will be used to interpret the example table. The next cell says that the fixture to use is called Calculator. In Java, the name of the fixture is the name of a Java class.
The second row, also know as the header row, designates the given columns and expected columns. In the example, dividend and divisor are given columns, whereas quotient? is an expected column.
The fixture code to support this example in Java is the class Calculator shown below.
Show me the code
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 4. Developer Guide. 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().
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.For the third row
carries out the following sequence of steps:- It assigns the value 6.0 to the dividend instance variable
- It assigns the value 2.0 to the divisor instance variable
- It calls the method quotient() of the fixture to get the value calculated by the system under development.
- It reads the value 3.0 from the quotient? column and compares it to the value returned by the fixture. Since the values are equal, it will annotate the cell as right, which results in the cell being colored green.
Same goes for the fourth and fifth rows.
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.
The Mortgage example described in Writing a Rule For specification document better illustrates the role of the fixture and its interactions with the system under development. This example is shown again here:
rule for | insurance premium fee calculation | ||
sale price | down payment | premium fee? | financed amount? |
$100,000 | $15,000 | $2,125.00 | |
$100,000 | $30,000 | $0.00 | |
$100,000 | $25,000 | $0.00 |
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:
public class InsurancePremiumFeeCalculationFixture { public Money salePrice; public Money downPayment; public Money premiumFee() { InsuranceFee insuranceFee = new InsuranceFee(salePrice); return insuranceFee.forDownpayment(downPayment); } public Money financedAmount() { return salePrice.minus(downPayment); } }
You might have noticed that the fixture class name is InsurancePremiumFeeCalculationFixture and not InsurancePremiumFeeCalculation. It does not matter to
. For fixture class names, the Fixture suffix can be added to make it easier to differentiate from the application under development code. will deal with it automatically.In this example, the fixture merely delegates the work to the application under development. Namely, the InsuranceFee domain object is responsible for performing the actual work.
The code for the system under development is:
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.This is done automatically for the most common built-in Java types. The mortgage example, however, uses a money type, which is defined by the application under development in the Money business domain class.
When
encounters a custom type while performing conversion, it will look for a public static method called parse to handle the conversion from a String to the custom type. To do the conversion from the custom type to a String, it will call the method toString() on the custom type.When
verifies an expected value to an actual value for a custom type, it does the following steps. It:- Converts the expected value specified as a string in the table to the custom type using the parse(String text) method.
- Compares the expected value and the actual value using the equals(Object other) method on the expected value, passing it the actual value.
Here's the relevant part of the code for the Money class:
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.
public class DomainStuffFixture { private DomainStuff stuff; private int x; private int y; public void setX(int x) { this.x = x; } public void setY() { this.y = y; } @BeforeFirstExpectation public void allocateDomainStuff() { stuff = DomainStuffService.allocateDomainStuff(x, y); } public int getComputedValueOne() { return stuff.getComputedValueOne(); } public int getOtherComputedValueOne() { return stuff.getOtherComputedValue(); } @AfterRow public void freeDomainStuff() { DomainStuffService.freeStuff(stuff); } // ... }
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.The second row of the table is the header row and is read from left to right. Two types of columns are supported in rule tables:
- 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,
:- figures out the public instance variable to use using camel casing rules.
- if there is no instance variable that matches the label of the column header, 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.
For an expected 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.
For a given column:
- It converts the text entered in the cell to the type of the instance variable. To do that it uses either built-in converters (for basic Java types) or delegates to a method called parse (for domain types).
- It assigns the converted value to the instance variable.
- However, if type conversion fails, the cell is reported as an error.
For an expected column:
- 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:
As expected, the row is returned by the system under development.
A row is missing or in surplus in the list returned by the system under development.
If the system encounters an execution error, the cell is in yellow, and
provides information about the error.Particular behavior for the list of interpreters:
Since the ListOfInterpreter expects to match exactly the expected list and the list returned by the SUD, compares each cell of the table separately. If the expected value is different from the returned value, colors the cell in red and provides the two values.
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.
When running the table,
uses a fixture to mediate between the example expressed in collection of values tables and the system under development. The fixture code defines how the specific lists of values are mapped to the application code.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.
list of | Canada Province Codes |
Name | Code |
ALBERTA | AB |
BRITISH COLUMBIA | BO |
NEW BRUNSWICK | NB |
MANITOBA | MB |
NEWFOUNDLAND and LABRADOR | NL |
NOVA SCOTIA | NS |
NUNAVUT | NU |
ONTARIO | ON |
PRINCE EDWARD ISLAND | PE |
QUEBEC | QC |
SASKATCHEWAN | SK |
YUKON | YT |
OTHER PROVINCE | OP |
The first cell of the first row indicates that a the ListOfInterpreter will be used to interpret the example table. The next cell says that the query() method to use is in the fixture named Canada Province Codes. In Java, the name of the fixture is the name of a Java class. The returned value of the query() method must be a Collection which is, in our example, a Set of Provinces.
The second row, also known as the header row, designates the attribute columns of the Collection's elements. In our example, Name and Code are the attribute of the Province.
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.public Collection query() {...}
The return type of the query method does not have to be specifically a Collection.
It can be any of the derived Collection class or an Array.
public Employees[] query() {...}
You can choose another method then query by telling GreenPepper which method to use with the help of an annotation or attributes.
import com.greenpepper.reflect.CollectionProvider; ... @CollectionProvider public Collection query() {...}
but the method specifies still have to be parameter-less and return a Collection implementation or an array.
Show me the code
public class CanadaProvinceCodesFixture { public Set<Province> query() { return Country.canada().provinces(); } }
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.For the third row
carries out the following sequence of steps:- It calls the method query() of the fixture CanadaProvinceCodesFixture to retrieve the Collection to test.
- It reads the value ALBERTA from the column Name and compares it to the attribute Name of the first element of the List.
Since the values are equal, it will annotate the cell as right, which results in the cell being colored green. - It reads the value AB from the column Code and compares it to the attribute Code of the first element of the List.
Since the values are equal, it will annotate the cell as right, which results in the cell being colored green.
Step 2 and 3 are repeated through the remaining rows of the table.
For the fourth row, the Codes are not the same so will mark the expected cell wrong. The cell will be colored red and will display a message including the expected value and the actual value.
The order of Provinces NEW BRUNSWICK and MANITOBA are inverted compared to the alphabetical sorting. will annotate both rows as wrong. Each cell will be colored red and will display a message including the expected value and the actual value.
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.
Consider the example of the Phone Book application as described in Collection Interpreters definition. The DoWithInterpreter will insert entries in the Phone Book. The ListOfInterpreter is then used to test that the Phone Book really contains the inserted entries.
1. We use the DoWithInterpreter to create our personal phone book.
do with | phone book | ||||
insert | Fred | Flintstone | with number | (123) 456-7890 | |
insert | Barney | Rubble | with number | (123) 321-7666 | |
insert | Great | Gazoo | with number | (123) 989-4455 |
2. The test to be performed is: The requirement list should be the same as the SUD list.
list of | Phone book entries |
FirstName | LastName |
Fred | Flintstone |
Betty | Rubble |
Great | Gazoo |
Wilma | Flintstone |
Show me the code
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(); } }
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:
When the action has been executed successfully,
color the cell(s) in green.If the action execution has failed,
color the cell(s) in red.If the system encounters an execution error, the cell is colored yellow and
provides information about the error.When the action has been executed successfully,
will display the returned value in gray.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.
When running the table,
uses a fixture to mediate between the example expressed in the sequence of tables and the system under development. The fixture code defines how the specific actions are mapped to the application code.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.
do with | bank |
open checking account | 12345-67890 | under the name of | Spongebob | Squarepants |
check | that balance of account | 12345-67890 | is | $0.00 |
end |
The first table indicates to use a DoWithInterpreter, which handles a business flow expressed as a sequence of tables. The fixture Bank will do the mediation with the application under development.
The interpreter will run all the tables until the end of the document. In this case, the second and third tables compose the business flow example.
The second table indicates to perform the action open checking account under the name of on the system under development, with the parameters 12345-67890, Spongebob and Squarepants. That action will result in a call to the method openCheckingAccountUnderTheNameOf() on the fixture with the parameters 12345-67890, Spongebob and Squarepants.
The third table indicates to check the result of the action that balance of account is on the system under development with the expected value $0.00. That action results in a call to the method thatBalanceOfAccountIs on the fixture with the parameter 12345-67890. That method is expected to return a value, which will be asserted for equality against the domain representation for the value $0.00.
The fixture code to support this example in Java is the class BankFixture shown below.
Show me the code
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 4. Developer Guide. It provides public instance methods openCheckingAccount and thatBalanceOfAccount to map respectively to the actions open checking account and that balance of account.
The fixture does not much except from delegating the processing to the application code, in this case the Bank class.
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.The second table is a default action, which
carries out in the following sequence of steps:- It calls the method openCheckingAccountUnderTheNameOf() with the parameters 12345-67890, Spongebob and Squarepants
- Since the method returns true, indicating a success, it marks the keyword cells as right, resulting in the first cell of the row being colored green.
The third table is a check action, which
carries out in the following sequence of steps:- 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.
do with | bank |
open checking account | 12345-67890 | under the name of | Spongebob | Squarepants |
check | that balance of account | 12345-67890 | is | $0.00 |
deposit | $100.00 | in account | 12345-67890 | |
check | that balance of account | 12345-67890 | is | $100.00 |
withdraw | $50.00 | from account | 12345-67890 | |
check | that balance of account | 12345-67890 | is | $50.00 |
reject | withdraw | $75.00 | from account | 12345-67890 |
check | that balance of account | 12345-67890 | is | $50.00 |
accept | withdraw | $25.00 | from account | 12345-67890 |
end |
The fourth and last example table also contains several 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.
In the first row of the last table, notice the use of the reject special keyword in the first cell. This indicates that the action for the row is expected to fail by either returning false or throwing an exception.
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:
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.
If an interpreter is specified in the first cell of a row, the remainder of the table will be processed by that interpreter. The action for the row must return a fixture that will be used to interpret the rest of the table.
In the example show below, the first row of the second table indicates to process the rest of the table using a SetOfInterpreter on the value returned by the action opened accounts.
do with | bank |
open checking account | 12345-67890 | under the name of | Spongebob | Squarepants | |
open savings account | 54321-09876 | under the name of | Patrick | Star |
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:
When the action has been executed successfully,
colors the row or words inside the row in green.If the action execution has failed,
colors the the row or words inside the row in red.If the system encounters an execution error, the row is colored yellow and
provides information about the error.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.
When running the table,
uses a fixture to mediate between the example expressed in the sequence of tables and the system under development. The fixture code defines how the specific actions are mapped to the application code.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.
scenario | bank |
open checking account 12345-67890 under the name of Spongebob Squarepants |
verify that balance of account 12345-67890 is $0.00 |
end |
The first table indicates to use a ScenarioInterpreter, which handles a business flow expressed as a sequence of tables. The fixture Bank will do the mediation with the application under development.
The interpreter will run all the tables until the end of the document. In this case, the second and third tables compose the business flow example.
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
@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
@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
@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
@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
@Display("[regular expression]")
A Display annotation will be use to show the result of the action (the actual result). The displayed value is only for information purpose. A good example will be to show an internal value. In our Bank example, this will represent showing the actual balance account.
If we continue with our example, the third table indicates to verify the balance account against the expected value $0.00. Here, we introduce a verification for an expected value and the actual value. This will be done using the Expectation object as a parameter of the method to be call.
The fixture code to support this example in Java is the class BankFixture shown below.
Show me the code
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 4. Developer Guide.
The fixture does not much except from delegating the processing to the application code, in this case the Bank class.
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.The second table is an action which
carries out in the following sequence of steps:- Find a method that match the action content using regular expression defined on annotated method
- The parameter Owner will be instanced by the type conversion facility and will contain Spongebob and Squarepants
- It calls the method openAccount() with the parameters 12345-67890 and the Owner object instance
- The behavior will be applied by the type of the annotation. In this case, it is a Given annotation and the row will not be colored green (except if an exception occur, the row will be colored red).
The third table is an action which
carries out in the following sequence of steps:- 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.
Scenario | bank |
open checking account 12345-67890 under the name of Spongebob Squarepants |
verify that balance of account 12345-67890 is $0.00 |
deposit $100.00 in account 12345-67890 |
verify that balance of account 12345-67890 is $100.00 |
withdraw $50.00 from account 12345-67890 |
verify that balance of account 12345-67890 is $50.00 |
can't withdraw $75.00 from account 12345-67890 |
verify that balance of account 12345-67890 is $50.00 |
can withdraw $25.00 from account 12345-67890 |
end |
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
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.
If an interpreter is specified in the first cell of a row, the remainder of the table will be processed by that interpreter. The action for the row must return a fixture that will be used to interpret the rest of the table.
In the example shown below, the first row of the second table indicates to process the rest of the table using a SetOfInterpreter on the value returned by the action opened accounts.
Scenario | bank |
open checking account 12345-67890 under the name of Spongebob Squarepants |
open savings account 54321-09876 under the name of Patrick Star |
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:
When the insertion has been executed successfully,
add a green cell at the end of the data row with the word entered.If the insertion has failed because the values specified does not respect a business rule,
add a red cell at the end of the row with the word not entered.If the insertion has failed because a specified value generates an error,
If the system encounters an error not related to a specific data, add a yellow cell at the end of the data row and provides information about the 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 first cell of the first row indicates that the SetupInterpreter will be used to interpret the table. The next cell says that the enterRow() method to use is in the fixture named AGroupOfCustomers. The name of the fixture is the name of a Java class. The enterRow() will be used to enter the data.
Instead of writing a method enterRow(), you can annotate the setup method with the annotation @EnterRow. This way you can have a meaningful name for the setup method.
The second row, also known as the header row, designates the attribute columns of the data to be inserted. In our example, number, type, first name, last name and balance are the information to be created in the system under development.
The fixture code to support this example in Java is the class AGroupOfCustomersFixture shown below.
Show me the code
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.
To change the 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.
provides a mechanism to help conversion from String to other data types and back again.
so an example that look likes | ||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Will match a class with the following method public class SomeFixture { public SomeFixture(Ammount ammount) {...} public setColor(RGB color) ... public someBool(boolean yesNo).. public Ratio answer()... } |
Lets look at how
will match these.Converters
Converters are class implementing the interface com.greenpepper.converter.TypeConverter in java.
public interface TypeConverter { boolean canConvertTo( Class type ); Object parse( String value, Class type ); String toString( Object value ); } namespace GreenPepper.Converters { public interface ITypeConverter { bool CanConvertTo(Type type); object ValueOf(string value, Type type); string ToString(object value); } }
provides out of the box type converters for the following types
- Integer
- BigDecimal
- Long
- Float
- Double
- Date
- Boolean
- Arrays
- String
or their simpler form int, long ...
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
public static void register( TypeConverter converter)
The better place to register your custom type is in a custom system under development :
public static class CustomSystemUnderDevelopment extends DefaultSystemUnderDevelopment { public CustomSystemUnderDevelopment( String... params ) { GreenPepper.register(new MyCustomTypeConverter()); } }
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.
The com.greenpepper.GreenPepper class provides these methods, too.
public static void unregisterLastAddedCustomConverter()
public static void unregisterAllCustomConverters()
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.
Self converting type implies that you add a static parse method to your class.
public static T parse(String val);
And then to revert back to a string,
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 FixturesThis 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)
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.
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.
If you want to build a runnable specification you can use the new SpecificationRunnerBuilder:
// ... 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()); SpecificationRunner runner = builder.build(); // ...
To execute a specification you can call the run method on the runner object or you can use the SpecificationRunnerExecutor to set some optional parameters (like the output file, debug mode, locale etc.):
// ... 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.
Now run your local test files with the following command:
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:
// ... 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.
Otherwise you would be forced to create a new com.greenpepper.Example class (like HTMLExample) and customize com.greenpepper.document.Document to handle your custom Example class.
Here is a example how you could write your custom DocumentBuilder (using http://markdownj.org/)
// ... 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).
Then return it as a File type and let the Repository build it as HTML-document.
Example with the WikiMarkupDocumentBuilder:
// ... 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.
The switch and Enum for the FileTypes has to be extend. Also you have to add the corresponding Method processing the filetype
Here is a example how you could extend the FileSystemRepository class:
// ... public Document loadDocument( String location ) throws Exception{ File file = fileAt( location ); if (!file.exists()) throw new DocumentNotFoundException( file.getAbsolutePath() ); switch (checkFileType( file )) { case HTML: return loadHtmlDocument( file ); case MARKUP: case CONFLUENCE: return loadWikimarkupDocument(file); default: throw new UnsupportedDocumentException( location ); } } // ... private Document loadWikimarkupDocument( File file ) throws IOException { return loadHtmlDocument(new WikimarkupDocumentBuilder().build(file)); } // ...
Example Enum:
//... 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.
An interpreter can have 1:n aliases. You can define them in a property file like the following example:
com.greenpepper.interpreter.DoWithInterpreter = Ablauf, Faire avec, девать, 做 com.greenpepper.interpreter.ScenarioInterpreter = Szenario, परिदृश्य, Scénario # ....
In general there are 3 ways to add an alias for an interpreter:
Programmatically (on runtime)
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.
- /myfolder/
- ...
- greenpepper-remote-agent-4.0.0-SNAPSHOT-complete.jar // Includes the greenpepper core jar
- aliases.properties
- ...
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.
Do With | |
---|---|
... |
Can now also be used like:
Ablauf | |
---|---|
.... |
Add Comment