Skip to end of metadata
Go to start of metadata

You are viewing an old version of this content. View the current version.

Compare with Current View Version History

« Previous Version 22 Next »

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
rule forFixtureName
....
list ofFixtureName
....
do withFixtureName
....

 

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
rule forcom.xyz.stuff.FixtureName
..

..

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
import
x.y.z

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
import
com.xyz.mystuff
list ofFixtureName
....

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
import
com.xyz.stuff
com.xyz.otherstuff
com.xyz.yapackage

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
the fixture                                                                                    TheFixture
a fixture with a very long name becomes                                    AFixtureWithAVeryLongName

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
rule forbank account fixture
......

and

rule forbank account
......

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
rule forbank account
......
public class BankAccountFixture {
   public BankAccountFixture()
   { ... }
}
An example with two parameters
rule forpoker tableante5$max bet

100$

...... 
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.
During the execution of the specification, compares the values returned by the system under development against the expected values defined by the Business Expert.

  • The first row of the table indicates the set of rules to be tested by .
  • The next row is called the header row and serves to distinguish the given values and the expected values. Given values serve as inputs to the system, whereas expected values serve as comparison values against values that are actually returned by the system. When a column header ends with special characters ? or (), it denotes an expected value.
  • Finally, the remaining rows capture the examples. They are the executable test rows.

Specific keywords for expected values

offers a list of useful keywords to support the Business Expert.

Empty cellsWhen a test cell is left blank, only shows the returned value
errorWhen 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:

Green

 When the expected value matches the returned value, the RuleForInterpreter colors the cell as "right" by coloring it green.

Red

If the values don't match, the RuleForInterpreter colors the cell as "wrong" in red.

YELLOW

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

Grey

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 forCalculator 
xysum?product()quotient?
1001000
639-5 

Output Table:

rule forCalculcator 
xysum?product()quotient?
100100

0


java.lang.ArithmeticException: / by zero
com.greenpepper.fixture.calculator.Calculator.quotient(Calculator.java:63)
639Expected: -5 Received: 182

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 forDivision 
dividenddivisorquotient?
6.02.03.0
723.5
183.06
1527

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

Code for the Calculator fixture
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:

  1. It assigns the value 6.0 to the dividend instance variable
  2. It assigns the value 2.0 to the divisor instance variable
  3. It calls the method quotient() of the fixture to get the value calculated by the system under development.
  4. 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 forinsurance premium fee calculation 
sale pricedown paymentpremium 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:

Code for the InsurancePremiumFeeCalculation fixture
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:

Code for the system under development
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:

  1. Converts the expected value specified as a string in the table to the custom type using the parse(String text) method.
  2. 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:

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.

Code with RowAction annotations
 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.

  • As for all other interpreters, the first row of the collection interpreters specifies the name of the interpreter and the name of the collection to be tested.
  • The next row is used to define the name of each attribute related to the members of the collection of data.
  • The following rows are used to specify the set of values to test.

Specific keywords

none

Coloring

will visually indicate the test result by coloring the rows:

Green

As expected, the row is returned by the system under development.

Red

A row is missing or in surplus in the list returned by the system under development.

YELLOW

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 ofCanada Province Codes
NameCode
ALBERTAAB
BRITISH COLUMBIABO
NEW BRUNSWICKNB
MANITOBAMB
NEWFOUNDLAND and LABRADORNL
NOVA SCOTIANS
NUNAVUTNU
ONTARIOON
PRINCE EDWARD ISLANDPE
QUEBECQC
SASKATCHEWANSK
YUKONYT
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

Code for the CanadaProvinceCodesFixture
public class CanadaProvinceCodesFixture
{
    public Set<Province> query()
    {
        return Country.canada().provinces();
    }
}
Code of the System Under Development
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:

  1. It calls the method query() of the fixture CanadaProvinceCodesFixture to retrieve the Collection to test.
  2. 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.
  3. 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 withphone book
insertFred Flintstonewith number(123) 456-7890
insertBarney Rubblewith number(123) 321-7666
insertGreat Gazoowith number(123) 989-4455

2. The test to be performed is: The requirement list should be the same as the SUD list.

list ofPhone book entries
FirstNameLastName
FredFlintstone
BettyRubble
GreatGazoo
WilmaFlintstone

Show me the code

Code for the PhoneBookFixture
public class PhoneBookFixture
{
    private PhoneBook phoneBook = new PhoneBook();
    public void insertWithNumber(String firstName, String lastName, String number)
    {
        phoneBook.insert(new PhoneBookEntry(firstName, lastName, number));
    }
    public List<PhoneBookEntry> query()
    {
        return phoneBook.entries();
    }
}
Code of the System Under Development
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.

  • 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.

Specific Keywords

offers a list of useful keywords to support the Business Expert. Those keywords are placed at the beginning of an action row.

AcceptConfirm that the action as been executed by the system under development.
CheckVerify the specified expected value with the value returned by the system under development
RejectThe action should not be performed by the system under development (expected errors).
DisplayPrint the value returned by the system under development.

Coloring

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

Green

When the action has been executed successfully, color the cell(s) in green.

Red

If the action execution has failed, color the cell(s) in red.

YELLOW

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

Grey

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.
AcceptOnly the cell containing the keyword Accept will be colored.
CheckThe cell containing the expected value will be colored. In case of a failure, will show the expected and the returned values.
RejectOnly the cell containing the keyword Reject will be colored.
DisplayA 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 withbank
open checking account12345-67890under the name ofSpongebob Squarepants
checkthat balance of account12345-67890is$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

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

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.

Code 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, 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:

  1. It calls the method openCheckingAccountUnderTheNameOf() with the parameters 12345-67890, Spongebob and Squarepants
  2. 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:

  1. It calls the method thatBalanceOfAccountIs() with the parameter 12345-67890 to get the value calculated by the system under test
  2. 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 withbank
open checking account12345-67890under the name ofSpongebob Squarepants
checkthat balance of account12345-67890is$0.00
deposit$100.00in account12345-67890
checkthat balance of account12345-67890is$100.00
withdraw$50.00from account12345-67890
checkthat balance of account12345-67890is$50.00
rejectwithdraw$75.00from account12345-67890
checkthat balance of account12345-67890is$50.00
acceptwithdraw$25.00from account12345-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:

Code 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.

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 withbank
open checking account12345-67890under the name ofSpongebob Squarepants
open savings account54321-09876under the name ofPatrick Star
set ofopened accounts
numbertypeowner name
12345-67890checkingSpongebob Squarepants
54321-09876savingsPatrick 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.

 


 

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

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

Green

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

Red

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

YELLOW

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

Grey

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.

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
@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

Code 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 4. Developer Guide.

The fixture does not much except from delegating the processing to the application code, in this case the Bank class.

Code 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, 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:

  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 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, 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 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, enter data in the system under development to create the desired state.

  • 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

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

Green

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

Red

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.

YELLOW

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.
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:

setupa group of customers
numbertypefirst namelast namebalance
11111-11111 checkingFredFlintstone$250.00
22222-22222 savingsFredFlintstone$10 000.00
44444-44444 savingsWilmaFlintstone$10 000.00
55555-55555 checkingBarneyRubble$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 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, 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 :

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
rule forSomeFixturewith parameter100$
colorsomeBoolanswer 
255,55,10yes14%
235,5,0no21%

 

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.

Remove last added converter
public static void unregisterLastAddedCustomConverter()
Remove 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.

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 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)
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

  1. Installed java runtime
  2. Your compiled fixture classes/jar (System under Test)
  3. Your compiled classes/jar (System under Development)
  4. Your specifications files
  5. greenpepper-cli-plugin-x.x.x.jar
  6. 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:

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:

greenpepper_alias_interpreters.properties
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
 
.... 


  • No labels

0 Comments

You are not logged in. Any changes you make will be marked as anonymous. You may want to Log In if you already have an account.