Fixture Conventions
Introduction
Even though one of the objectives of
is to create a common language between the business experts and the development team, there will always be a certain degree of difference between the natural language and the programing language. Hence comes the reason for having fixtures. Fixtures are the glue between the business expert examples and the software being developed. When running the table, uses a fixture to mediate between the example expressed in the table and the system under test.Collaboration demands Compromise
The goal of the fixture, is to translate from one language to the other so neither has to compromise their clarity or their design to match the other. The fixture is the compromise. A fixture is any class. It does not have to extend or implement any base class/interface.
The fixture name
The fixture name is found right next to the interpreter specification.
simple fixture name | ||||||||||||||
|
|
|
Since a fixture is a Java class, when GreenPepper executes the example, it will try to match the fixture name with a class name.
What about packages?
Usually, Java classes are found inside a package and we can explicitly load a class via the package name.
An explicitly imported fixture | ||||
---|---|---|---|---|
|
Readability
The problem with packages and namespace and with the classes naming convention, camel casing and no spaces, is that they makes the example less readable for the business expert.
To help readability, we have the following options:
Import tables
Import tables are special tables at the beginning of the document.
By using an import table we can remove the package from the fixture name thus improving the readability of the example.
an import table | ||
---|---|---|
|
When GreenPepper will search for fixtures in the code, it will look into all the packages specified in the import tables.
Example of fixture name with implicit import | ||||||
---|---|---|---|---|---|---|
GreenPepper will match com.xyz.stuff.FixtureName. |
You can have more than one package or namespace imported in a document. Just add more lines to the import table.
an import table with multiple imports | ||||
---|---|---|---|---|
|
GreenPepper will search for the fixture in each of these packages until it finds a matching class.
Humanized name
Event without the package, programmatic naming conventions are not the most readable form for the name of the example. This can be arranged by following the camel casing conventions.
Use a free form with space separating each word for the fixture name and make the fixture class use camel casing.
GreenPepper will match the words of the fixture name with the camel cased class name.
Camel casing multiple word fixture name |
---|
fixture name |
When using the humanized version of fixture naming, you must use Developer Guide (Under Construction).
Explicit package won't work ex: |list of | com.xyz.stuff.the fixture name|
The fixture suffix
If you wish to clearly distinguish between your domain classes and the classes that serve as fixture, you can add the suffix Fixture at the end of the fixture classes name.
When writing the example, you can omit the suffix Fixture from the fixture name. This keeps the example closer to the real domain.
Suffix example | ||||||||
---|---|---|---|---|---|---|---|---|
and
will both match public class BankAccountFixture{ ... } |
Constructor
A fixture can receive parameters during it's construction. You must have a public constructor that matches the numbers of parameters.
When no parameters are specified in the example, the fixture class must have a public constructor without parameters.
Empty constructor example | ||||
---|---|---|---|---|
public class BankAccountFixture { public BankAccountFixture() { ... } } |
An example with two parameters | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
public class PokerTableFixture { public PokerTableFixture(Ammount ante, Ammount maxBet) { ... } } |
Rule validation (Rule For)
Definition
Definition |
---|
The RuleForInterpreter is used to express concrete and measurable business rules.
|
Specific keywords for expected values
offers a list of useful keywords to support the Business Expert.
Empty cells | When a test cell is left blank, | only shows the returned value
---|---|
error | When you expect an error, specify it in the cell to test that particular behavior |
Coloring
will visually show the test result by coloring each testing cell:
When the expected value matches the returned value, the RuleForInterpreter colors the cell as "right" by coloring it green.
If the values don't match, the RuleForInterpreter colors the cell as "wrong" in red.
If the system encounters an execution error, the cell is colored yellow and
provides information about the error.If no expected value is specified in a test, the RuleForInterpreter colors the cell in gray and displays the returned value.
Here is an example of cell coloring:
Input Table:
rule for | calculator | |||
---|---|---|---|---|
x | y | sum? | product() | quotient? |
10 | 0 | 10 | 0 | 0 |
6 | 3 | 9 | -5 |
Output Table:
rule for | calculcator | |||
---|---|---|---|---|
x | y | sum? | product() | quotient? |
10 | 0 | 10 | 0 | 0 java.lang.ArithmeticException: / by zero |
6 | 3 | 9 | Expected: -5 Received: 18 | 2 |
Writing fixtures for Rule tables
As we've seen in the Rule For definition, a table of rules is used to express business rules of the application under development.
A fixture for a table of rules defines how the specific given and expected columns of a rule table are mapped to the system under development.
This page shows the fixture code that supports the examples introduced in the Writing a Rule For specification(link to-do) documentation.
Fixture for Calculator
Consider the first example of business rule table described in Table of Rules documentation, shown again below.
rule for | Division | |
dividend | divisor | quotient? |
6.0 | 2.0 | 3.0 |
7 | 2 | 3.5 |
18 | 3.0 | 6 |
15 | 2 | 7 |
The first cell of the first row indicates that a RuleForInterpreter will be used to interpret the example table. The next cell says that the fixture to use is called Calculator. In Java, the name of the fixture is the name of a Java class.
The second row, also know as the header row, designates the given columns and expected columns. In the example, dividend and divisor are given columns, whereas quotient? is an expected column.
The fixture code to support this example in Java is the class Calculator shown below.
Show me the code
public class Division { public double dividend; public double divisor; public double quotient() { return dividend / divisor; } }
That class follows the general rules of fixtures described in the Fixture Conventions. It exposes the instance variable dividend to map with the given column dividend. The given column divisor corresponds to the public instance variable divisor. The expected column quotient? is mapped to the public instance method quotient().
This is a very simple example in which the fixture object does not call on the system under development, but performs the calculation. The quotient() method uses the instance variables dividend and divisor to perform the calculation by itself.
How is the table processed?
When it runs this table,
reads the first row to select the interpreter and fixture. It then reads the second to know what are the given and expected columns. Finally it starts testing from the third row down to the end of the table.For the third row
carries out the following sequence of steps:- It assigns the value 6.0 to the dividend instance variable
- It assigns the value 2.0 to the divisor instance variable
- It calls the method quotient() of the fixture to get the value calculated by the system under development.
- It reads the value 3.0 from the quotient? column and compares it to the value returned by the fixture. Since the values are equal, it will annotate the cell as right, which results in the cell being colored green.
Same goes for the fourth and fifth rows.
On the other hand, on the last row the comparison will fail.
will mark the expected cell wrong. That cell will be colored red and will display a message including the expected value and the actual value.How are the types of the values handled?
The instance variables dividend and divisor are of type double, so the given values in the example table needs to be double as well. For the fourth row,
will automatically convert the given values 7 and 2 to doubles before assigning them to the instance variables.The return value of the quotient method is also a double, which means that the values provided in the quotient? expected column must be doubles as well. When
compares the value returned by the fixture with the value provided as an expectation, it will first convert the expected value to the type of the actual returned value. In the fifth row, it will convert the value 6 to a double and then do the comparison.A More Realistic Example
The Calculator example is very simple. In a real world example, the fixture code would not perform any real work but would instead delegate to the application under development. In other words, if this was a real application the fixture would not carry out the division operation but call on the system under development. The general rule is to keep the fixture as thin as possible to be merely a mediator between the example table and the application code.
The Mortgage example described in Writing a Rule For specification document better illustrates the role of the fixture and its interactions with the system under development. This example is shown again here:
rule for | insurance premium fee calculation | ||
sale price | down payment | premium fee? | financed amount? |
$100,000 | $15,000 | $2,125.00 | |
$100,000 | $30,000 | $0.00 | |
$100,000 | $25,000 | $0.00 |
This example uses the InsurancePremiumFeeCalculation fixture class. The two given columns, which are sale price and down payment, are mapped respectively to the instance variables salePrice and downPayment. The two expected columns, called premium fee? and financed amount?, correspond respectively to the methods premiumFee() and financedAmount().
How are spaces between words handled?
You have noticed that you can separate words in the example table with spaces.
will convert the sequence a space-separated words to a valid Java identifier by removing the spaces and capitalizing the first letter of every word except the first one. This is called camel casing.- For class names, uses upper camel casing with the first letter of the identifier capitalized as well. In our example, the label insurance premium fee calculation is converted to the class name InsurancePremiumFeeCalculation.
- For method or instance variable names, uses lower camel casing where the first letter of the identifier is left in lowercase.
Show me the code
The supporting code is here:
public class InsurancePremiumFeeCalculationFixture { public Money salePrice; public Money downPayment; public Money premiumFee() { InsuranceFee insuranceFee = new InsuranceFee(salePrice); return insuranceFee.forDownpayment(downPayment); } public Money financedAmount() { return salePrice.minus(downPayment); } }
You might have noticed that the fixture class name is InsurancePremiumFeeCalculationFixture and not InsurancePremiumFeeCalculation. It does not matter to
. For fixture class names, the Fixture suffix can be added to make it easier to differentiate from the application under development code. will deal with it automatically.In this example, the fixture merely delegates the work to the application under development. Namely, the InsuranceFee domain object is responsible for performing the actual work.
The code for the system under development is:
public class InsuranceFee { private final Money salePrice; public InsuranceFee(Money salePrice) { this.salePrice = salePrice; } public Money forDownpayment(Money downPayment) { return downPayment.greaterThan(salePrice.times(Ratio.percent(25))) ? Money.zero() : financedAmount( downPayment ).times( Ratio.of(25, 1000) ); } private Money financedAmount(Money downPayment) { return salePrice.minus( downPayment ); } }
What about the empty column?
The financed amount? expected value is left empty to tell
that this is an information column. We don't want to perform any test in that column, but rather show the value to help our understanding of the calculation. The calculated values will be shown when the example is run and marked with the ignored annotation, making them appear gray.How are application value types handled?
We've seen that
will automatically convert the given values in the table to the type of the corresponding instance variable.This is done automatically for the most common built-in Java types. The mortgage example, however, uses a money type, which is defined by the application under development in the Money business domain class.
When
encounters a custom type while performing conversion, it will look for a public static method called parse to handle the conversion from a String to the custom type. To do the conversion from the custom type to a String, it will call the method toString() on the custom type.When
verifies an expected value to an actual value for a custom type, it does the following steps. It:- Converts the expected value specified as a string in the table to the custom type using the parse(String text) method.
- Compares the expected value and the actual value using the equals(Object other) method on the expected value, passing it the actual value.
Here's the relevant part of the code for the Money class:
public class Money { private final BigDecimal dollars; public Money(BigDecimal dollars) { this.dollars = dollars; } public static Money parse(String text) { return new Money( new BigDecimal( normalize( text ) ) ); } private static String normalize(String text) { return text.replaceAll( "\\$", "").replaceAll( ",", "").replaceAll( "\\s", ""); } // ... public boolean equals(Object other) { if (other instanceof Money) { Money that = (Money) other; return this.dollars.compareTo( that.dollars ) == 0; } else { return false; } } public int hashCode() { return dollars.hashCode(); } public String toString() { return "$" + dollars; } // ... }
Row actions in ruleFor
There are 3 annotations(attributes) that can be used in the fixture with a RuleForInterpreter.
- BeforeRow
- AfterRow
- BeforeFirstExpectation
BeforeRow
A method annotated with BeforeRow will be call at the beginning of each new row so you can prepare your fixture.
AfterRow
A method annotated with AfterRow will be call at the end of each row so you can reset your fixture.
BeforeFirstExpectation
The method annotated with BeforeFirstExpectation will be called on each row just before the first ExpectedColumn(column with a ?) is encounterd so you can prepared the return values for this row.
public class DomainStuffFixture { private DomainStuff stuff; private int x; private int y; public void setX(int x) { this.x = x; } public void setY() { this.y = y; } @BeforeFirstExpectation public void allocateDomainStuff() { stuff = DomainStuffService.allocateDomainStuff(x, y); } public int getComputedValueOne() { return stuff.getComputedValueOne(); } public int getOtherComputedValueO() { return stuff.getOtherComputedValue(); } @AfterRow public void freeDomainStuff() { DomainStuffService.freeStuff(stuff); } // ... }
The three annotations are optional, you can use none, some or all of them in your fixture depending on your needs.
Wrap Up
When
runs a rule table example, it creates a fixture class from the name indicated in the second cell of the first row. That class is used to mediate between the development table and the application code when checking the example against the system under development.The second row of the table is the header row and is read from left to right. Two types of columns are supported in rule tables:
- given values, which are mapped to public instance variables in the fixture class
- expected values, which are checked against values returned by the corresponding public instance methods
Headers
For a given column header cell,
:- figures out the public instance variable to use using camel casing rules.
- if there is no instance variable that matches the label of the column header, it marks the cell with an error annotation, causing the cell to appear yellow and to display a stack trace of the error. That column will be ignored entirely.
For an expected column header cell:
- It figures out the public instance method to use using camel casing rules.
- If there is no such method or if it's not publicly accessible, it marks the cell with an error annotation, causing the cell to appear yellow and to display a stack trace of the error. That column will be ignored entirely.
Columns
will then run all remaining rows one at a time, going through all cells in the row from left to right.
For a given column:
- It converts the text entered in the cell to the type of the instance variable. To do that it uses either built-in converters (for basic Java types) or delegates to a method called parse (for domain types).
- It assigns the converted value to the instance variable.
- However, if type conversion fails, the cell is reported as an error.
For an expected column:
- It converts the text entered in the cell to the return type of the instance method. To do that it uses either built-in converters (for basic Java types) or delegates to a method called parse (for domain types).
- It compares the actual value to the converted value based on the definition of the equality for the type.
- If the values match, it marks the cell with a right annotation causing it to appear green.
- If the values do not match, it marks the cell with a wrong annotation so that it appears red and displays the expected and actual values.
- However, if type conversion fails or if the method throws an exception the cell is reported as an error.
List Validation (List of, Set of, Superset of, Subset of)
Definition
DEFINITION |
---|
The collection interpreters are used to express any kind of groups, lists or sets of values. When a collection interpreter is executed, compares the list of values expected with the list of values returned by the system under development. The test result depends on the specific collection interpreter selected.
|
Specific keywords
none
Coloring
will visually indicate the test result by coloring the rows:
As expected, the row is returned by the system under development.
A row is missing or in surplus in the list returned by the system under development.
If the system encounters an execution error, the cell is in yellow, and
provides information about the error.Particular behavior for the list of interpreters:
Since the ListOfInterpreter expects to match exactly the expected list and the list returned by the SUD, compares each cell of the table separately. If the expected value is different from the returned value, colors the cell in red and provides the two values.
Particular behavior for the superset of interpreters:
Since the SupersetOfInterpreter accepts that the specification may contain rows that are not contained in the SUD, will not color the rows contained in the specification but not returned by the SUD.
Writing fixtures for List tables
Writing fixtures for List of Value
As we've seen in the Collection Interpreters definition, 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
0 Comments