...
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.
|
...
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 |
Workflow validation (Scenario)
Context definition (Setup)
Definition
Writing fixtures for Setup tables
Advanced
Defining a custom system under development
Customizing GreenPepper fixture resolution
Hooking document execution
Execute specifications with command line
Examples
Guice Example
Rest fixture
Spring example
...
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, colors the row or words inside the row in green. |
Panel | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
If the action execution has failed, colors the the row or words inside the row in red. |
Panel | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
If the system encounters an execution error, the row is colored yellow and provides information about the error. |
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.
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(link to-do).
Fixture for Bank
Consider the first example of business flow described in Writing a Scenario specification(link to-do), 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
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]") |
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
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).
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.
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(link to-do), 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
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.
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:
Panel | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
When the insertion has been executed successfully, add a green cell at the end of the data row with the word entered. |
Panel | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
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. |
Panel | ||||||||
---|---|---|---|---|---|---|---|---|
| ||||||||
If the insertion has failed because a specified value generates an error, colors the cell of the data in error yellow 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(link to-do).
Fixture to create bank customers
Consider the example of setup table described in Writing a Setup specification(link to-do):
setup | a group of customers | |||
number | type | first name | last name | balance |
11111-11111 | checking | Fred | Flintstone | $250.00 |
22222-22222 | savings | Fred | Flintstone | $10 000.00 |
44444-44444 | savings | Wilma | Flintstone | $10 000.00 |
55555-55555 | checking | Barney | Rubble | $999.54 |
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
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 carries out the following sequence of steps:
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 :
- With GreenPepper Server : Server system under development configuration(link to-do)
- With GreenPepper Open : Command line system under development configuration(link to-do)
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
|
Lets look at how will match these.
Converters
Converters are class implementing the interface com.greenpepper.converter.TypeConverter in java.
Code Block | ||
---|---|---|
| ||
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
Code Block | ||
---|---|---|
| ||
public static void register( TypeConverter converter) |
The better place to register your custom type is in a custom system under development :
Code Block | ||
---|---|---|
| ||
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.
Code Block | ||||
---|---|---|---|---|
| ||||
public static void unregisterLastAddedCustomConverter() |
Code Block | ||||
---|---|---|---|---|
| ||||
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.
Code Block | ||
---|---|---|
| ||
public static T parse(String val); |
And then to revert back to a string,
Code Block | ||
---|---|---|
| ||
public static String toString(T value) |
Rules of conversion
From example to fixture
1 First will verify if the type is can self convert (i.e. public static T parse(String) or public static T ValueOf(string))
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 will verify if the type is can self revert (i.e. public static String toString(T) or public static T ToString(string))
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
}
} |