Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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.

  • As for all other interpreters, the first row of the DoWithInterpreter specifies the name of the interpreter and the name of the sequence of actions to be tested. What makes the DoWithInterpreter particular is that it only has to be defined once for all the sequences of actions expressed in a page. Obviously, the DoWithInterpreter must be define before any sequence of actions.
  • The following rows are used to express specific actions.
  • The form of each row of a DoWithInterpreter shall respect the following rules:
    • a row shall begin with a part of the action description,
    • each parameter shall be isolated in a cell,
    • each parameter shall be separated by parts of the action description.
  • An action description can be left blank in order to separate two parameters.
  • The DoWithInterpreter provides a minimum of keywords used to define a specific action.
  • The DoWithInterpreter may also be expressed in Bullet List form or Number List form.

...

Code Block
languagejava
titleCode for the Bank fixture
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 ofopened accounts
numbertypeowner name
12345-67890checkingSpongebob Squarepants
54321-09876savingsPatrick 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, Image Added confirms that each action has successfully been performed.

 


 

ScenarioIdentification of the set of rule
Action 1

 

Action 2

...
Action i

 


 

  • As for all other interpreters, the first row of the ScenarioInterpreter specifies the name of the interpreter and the name of the sequence of actions to be tested. What makes the ScenarioInterpreter particular is that it only has to be defined once for all the sequences of actions expressed in a page. Obviously, the ScenarioInterpreter must be define before any sequence of actions.
  • The ScenarioInterpreter may also be expressed in Bullet List form or Number List form.

Coloring

Image Added will visually show the test result by coloring a complete row or words inside the row:

Panel
bgColorlightgreen
titleBGColorlightgreen
titleGreen
borderStyledashed

When the action has been executed successfully, Image Added colors the row or words inside the row in green.

Panel
bgColor#f08080
titleBGColor#f08080
titleRed
borderStyledashed

If the action execution has failed, Image Added colors the the row or words inside the row in red.

Panel
bgColor#f0e68c
titleBGColor#f0e68c
titleYELLOW
borderStyledashed

If the system encounters an execution error, the row is colored yellow and Image Added provides information about the error.

Panel
bgColorlightgrey
titleBGColorlightgrey
titleGrey
borderStyledashed

When the action has been executed successfully, Image Added 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, Image Added 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.

scenariobank
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
bgColorlightgrey
borderStyledashed
@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
bgColorlightgrey
borderStyledashed
@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
bgColorlightgrey
borderStyledashed
@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
bgColorlightgrey
borderStyledashed
@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
bgColorlightgrey
borderStyledashed
@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
languagejava
titleCode for the Bank fixture using annotations for the Scenario Interpreter
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
languagejava
titleCode for the Bank application code
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, Image Added 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 Image Added carries out in the following sequence of steps:

  1. Find a method that match the action content using regular expression defined on annotated method
  2. The parameter Owner will be instanced by the type conversion facility and will contain Spongebob and Squarepants
  3. It calls the method openAccount() with the parameters 12345-67890 and the Owner object instance
  4. 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 Image Added carries out in the following sequence of steps:

  1. Find a method that match the action content using regular expression defined on annotated method
  2. 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
  3. 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.

Scenariobank
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
languagejava
titleCode for the Bank fixture
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.

Scenariobank
open checking account 12345-67890 under the name of Spongebob Squarepants
open savings account 54321-09876 under the name of Patrick Star
set ofopened accounts
numbertypeowner name
12345-67890checkingSpongebob Squarepants
54321-09876savingsPatrick 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, Image Added enter data in the system under development to create the desired state.

Image Added

  • The first row of the table indicates the name of the interpreter and the name of the desired state.
  • The second row is called the header row and serves to identify the data to be inserted in the system under development.
  • Finally, the remaining rows captures the data to be inserted.

Coloring

Image Added will visually show the test result by coloring each testing cell:

Panel
bgColorlightgreen
titleBGColorlightgreen
titleGreen
borderStyledashed

When the insertion has been executed successfully, Image Added add a green cell at the end of the data row with the word entered.

Panel
bgColor#f08080
titleBGColor#f08080
titleRed
borderStyledashed

If the insertion has failed because the values specified does not respect a business rule, Image Added add a red cell at the end of the row with the word not entered.

Panel
bgColor#f0e68c
titleBGColor#f0e68c
titleYELLOW
borderStyledashed

If the insertion has failed because a specified value generates an error, Image Added colors the cell of the data in error yellow and provides information about the error.
If the system encounters an error not related to a specific data, Image Added 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(link to-do).

Fixture to create bank customers

Consider the example of setup table described in Writing a Setup specification(link to-do):

setupa group of customers
numbertypefirst namelast namebalance
11111-11111checkingFredFlintstone$250.00
22222-22222savingsFredFlintstone$10 000.00
44444-44444savingsWilmaFlintstone$10 000.00
55555-55555checkingBarneyRubble$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
languagejava
titleCode for the creation of a group of customers fixture
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, Image Added 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 Image Added 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 Image Added 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 :

Using custom types

All the examples in the documents are in strings, but fixtures want to process and return other data types.

Image Added provides a mechanism to help conversion from String to other data types and back again.

so an example that look likes
rule forSomeFixturewith parameter100$
colorsomeBoolanswer 
255,55,10yes14%
235,5,0no21%

 

Will match a class with the following method

Code Block
languagejava
public class SomeFixture {
   public SomeFixture(Ammount ammount) {...}

   public setColor(RGB color) ...
   public someBool(boolean yesNo)..
   public Ratio answer()...
}

Lets look at how Image Added will match these.

Converters

Converters are class implementing the interface com.greenpepper.converter.TypeConverter in java.

Code Block
languagejava
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);
    }
}

Image Added 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
languagejava
public static void register( TypeConverter converter)

The better place to register your custom type is in a custom system under development :

Code Block
languagejava
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 Image Added 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
languagejava
titleRemove last added converter
public static void unregisterLastAddedCustomConverter()
Code Block
languagejava
titleRemove All Added Converters
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
languagejava
public static T parse(String val);

And then to revert back to a string,

Code Block
languagejava
public static String toString(T value)

Rules of conversion

From example to fixture

1 First Image Added 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 Image Added 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 Image Added 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 Image Added to resolve your fixtures.

Only for specifying location to resolve fixtures (packages in java)
Code Block
languagejava
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 Image Added 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
languagejava
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 with command line