Decision Table: A table where each row represents a test case. A column can contain either a test input or an expectation. The rows can be executed either sequential or in parallel.
| value a | value b | a + b = ? |
|---|---|---|
1 |
2 |
3 |
-1 |
2 |
1 |
In this example the columns value a and value b are test inputs and the column
a + b = ? is an expectation.
The execution of a decision table follows a very specific life cycle. It is comparable to the phases of a unit test class:
-
before table -
create fixture instance (parallel or not)
-
before row -
set inputs
-
before first check -
execute checks
-
after row -
after table
Given the example at the beginning of this chapter, the execution would be as follows:
-
execute
before tablemethods -
create new fixture instance (parallel or not)
-
execute
before rowmethods -
set input matching
value ato1 -
set input matching
value bto2 -
execute
before first checkmethods -
execute check matching
a + b = ?with expectation3 -
execute
after rowmethods -
create new fixture instance
-
execute
before rowmethods -
set input matching
value ato-1 -
set input matching
value bto2 -
execute
before first checkmethods -
execute check matching
a + b = ?with expectation1 -
execute
after rowmethods -
execute
after tablemethods
When executing a decision table, there are multiple points where exceptions can occur.
-
An exception in a
before tablemethod will stop the table from being executed with the exception ofafter tablemethods. -
An exception in a
before rowmethod will stop the row from being executed with the exception ofafter rowmethods. -
An exception in
set inputwill stop the row from being executed any further with the exception ofafter rowmethods. -
An exception in a
before first checkmethod will stop the row from being executed any further with the exception ofafter rowmethods. -
An exception in a
execute checkmethod will fail the check but continue the row execution.
LivingDoc distinguishes exceptional cases between AssertionError and any
other Exception:
The occurrence of an AssertionError in set input or execute check will
result in the this step being marked as failed. The occurrence of any other
Exception will mark it with exception.
The occurrence of any Exception in before row, before first check or
after row will mark the entire row with exception. As will the occurrence
of any Exception in before table or after table mark the entire table
with exception.
This annotation is used to declare a class as a decision table fixture that contains all necessary steps.
The annotation allows the specification of a parameter parallel with a boolean. The parameter is set to false
by default. That means the rows of the decision table fixture will executed in sequential order.
If parallel is set to true, the rows will be executed in parallel.
@DecisionTableFixture(parallel = true)
class ParallelDecisionTableFixture {
...
}@DecisionTableFixture(parallel = false)
class SequentialDecisionTableFixture {
...
}or
@DecisionTableFixture
class SequentialDecisionTableFixture {
...
}The annotated class must be static.
This annotation is used on static methods in order to bind them to the life
cycle phase before table. The annotated method must be static and is not
allowed to have any parameters.
These methods are generally used to execute setup code only once before the table is evaluated. This could be the initialization of a browser or start of a server. Basically any expensive required setup.
Multiple methods can be bound by this annotation. The order in which they are executed is non-deterministic. As a general rule there should not be any chronological dependency between these methods.
@BeforeTable
static void createTestData() {
...
}This annotation is used on instance methods in order to bind them to the life
cycle phase before row. The annotated method must not be static and is
not allowed to have any parameters.
These methods are generally used to execute setup code for each test case. As an example, this could be the reset of the system under test.
Multiple methods can be bound by this annotation. The order in which they are executed is non-deterministic. As a general rule there should not be any chronological dependency between these methods.
@BeforeRow
void resetState() {
...
}This annotation is used on instance fields or methods in order to set a test input value on the fixture. If it is used on a method, this method must not be static and must have exactly one parameter.
For most test inputs it should be enough to annotate the corresponding field. Input methods are often used when additional logic is required to set the actual value (e.g. loading a value from a database).
This annotation can be used multiple times on the same target in order to define additional mappings for this test input. This can be useful if multiple languages or alternative spelling need to be supported.
@Input("value a")
void setValueA(Double valueA) {
this.valueA = valueA;
}
@Input("value b")
Double valueB;This annotation is used on instance methods in order to bind them to the life
cycle phase before first check. The annotated method must not be static
and is not allowed to have any parameters.
These methods are generally used in order to gather same data used by the checks in case gathering that data inside the check methods would take too long. This is often the case when the fixture is based on UI-Interactions.
Multiple methods can be bound by this annotation. The order in which they are executed is non-deterministic. As a general rule there should not be any chronological dependency between these methods.
@BeforeFirstCheck
void gatherDataForChecks() {
...
}This annotation is used on instance methods in order to bind that method to an expectation column of the decision table. The annotated method must not be static and must have exactly one parameter.
These methods must execute some assertion logic to verify that the actual value resulting from some action is equal to the expressed expectation provided as the parameter of the method.
This annotation can be used multiple times on the same target in order to define additional mappings for this check. This can be useful if multiple languages or alternative spelling need to be supported.
@Check("a + b = ?")
void checkSum(Double expectedValue) {
Double actualValue = ...
assertThat(actualValue).isEqualTo(expectedValue);
}This annotation is used on instance methods in order to bin them to the life
cycle phase after row. The annotated method must not be static and is
not allowed to have any parameters.
These methods are generally used to cleanup whatever was created in a
corresponding @BeforeRow method.
Multiple methods can be bound by this annotation. The order in which they are executed is non-deterministic. As a general rule there should not be any chronological dependency between these methods.
@AfterRow
void resetState() {
...
}This annotation is used on static methods in order to bind them to the life
cycle phase after table. The annotated method must be static and is not
allowed to have any parameters.
These methods are generally used to cleanup whatever was created in a
corresponding @BeforeTable method.
Multiple methods can be bound by this annotation. The order in which they are executed is non-deterministic. As a general rule there should not be any chronological dependency between these methods.
@AfterTable
void deleteTestData() {
...
}link:{moduleBase}/src/main/java/examples/decisiontables/CalculatorFixture.java[role=include]The following tables lists some benchmark results. The benchmarks were run on an i5-5200U with 8GB of RAM.
Benchmark |
Execution Mode |
Number of Rows |
Average Time (ms) |
Simple Operation |
sequential |
1,000 |
13.111 |
Simple Operation |
sequential |
1,000,000 |
14456.335 |
Simple Operation |
parallel |
1,000 |
5.456 |
Simple Operation |
parallel |
1,000,000 |
8900.850 |
Expensive Operation |
sequential |
10 |
7987.379 |
Expensive Operation |
parallel |
10 |
4845.735 |