Rule For: Rule Validation (Developer)
Definition
The RuleForInterpreter is used to express concrete and measurable business rules.
During the execution of the specification, GreenPepper 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 GreenPepper.
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
GreenPepper offers a list of useful keywords to support the Business Expert.
Empty cells | When a test cell is left blank, GreenPepper only shows the returned value |
---|---|
error | When you expect an error, specify it in the cell to test that particular behavior |
Coloring
GreenPepper will visually show the test result by coloring each testing cell:
GREEN
When the expected value match 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 GreenPepper provides information about the error.
GRAY
If no expected value is specified in a test, the RuleForInterpreter colors the cell in gray.
Here is an example of cell coloring:
rule for | calculator | |||
---|---|---|---|---|
x | y | sum? | product() | quotient? |
10 | 0 |
10
|
0
|
Division by zero
|
6 | 3 |
9
|
-5
|
Writing fixtures for Rule Tables
As we've seen in Rule For definition, a table of rules is used to express business rules of the application under development.
A fixture for a table of rules defines how the specific given and expected columns of a rule table are mapped to the system under development.
This page shows the fixture code that supports the examples introduced in the Writing a Rule For specification documentation.
Fixture for Calculator
Consider the first example of business rule table described in Table of Rules documentation, shown again below.
rule for | Division | |
---|---|---|
dividend | divisor | quotient? |
6.0 | 2.0 | 3.0 |
7 | 2 | 3.5 |
18 | 3.0 | 6 |
15 | 2 | 7.5 |
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 1. Fixtures 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, GreenPepper 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 GreenPepper 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. GreenPepper 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, GreenPepper 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 GreenPepper 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. GreenPepper 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, GreenPepper 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, GreenPepper 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 GreenPepper 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 GreenPepper 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 GreenPepper 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 GreenPepper 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 GreenPepper 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:
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 call on each row just before the first ExpectedColumn(column with a ?) is encounter so you can prepared the return values for this row.
The three annotations are optionals, you can use none, some or all of them in your fixture dependiong on your needs.
Let's Wrap Up
When GreenPepper 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, GreenPepper:
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
GreenPepper 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.