This is a demonstration of how to implement a Cucumber test for a derived static attribute.
You may have previously read the article about an Integration test for a Derived Attribute in which I tested the derivation logic of the attribute BB.TEMPLATE to ensure it returns the correct template depending on ADO type, product class etc. The implementation there was based on JUnit.
This time, I am implementing the same test (although much more exhaustive) based on the Cucumber framework.
A basic feature file
Let’s have a look at a basic feature file first, before I go about implementing the underlying
steps. If you want to follow along, you should create a file src/test/features/bb_template.feature
in your project with the following content:
Feature: Bloomberg attribute BB.TEMPLATE derives template correctly
Scenario: BB ADO with Ado Type I and Product A
Given an Ado with prefix BB
And we set BB_FLW_ADO_TYPE to I
And we set BB_FLW_PRODUCT to A
When we read the value of BB.TEMPLATE
Then we get BB+COM_LSA
What I have here, is a feature file to test the BB.TEMPLATE derivation logic. It currently has a single scenario in it. Let’s take this scenario apart and think about what needs to happen in each step. This will guide the implementation in the next section:
Given an Ado with prefix BB
Creates a test ADO with prefix BB.And we set BB_FLW_ADO_TYPE to I
Sets the attribute BB_FLW_ADO_TYPE on that test ADO to I.And we set BB_FLW_PRODUCT to A
Sets the attribute BB_FLW_PRODUCT on that test ADO to A.
Both of the steps above will have triggered the attribute BB.TEMPLATE.When we read the value of BB.TEMPLATE
We retrieve the value of BB.TEMPLATE and keep it for the next step.Then we get BB+COM_LSA
Finally, we ensure the value retrieved above is BB+COM_LSA.
Implementing the steps
Dependencies
Before I can start to implement the steps, I need to add the following dependencies:
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java8</artifactId>
<version>1.2.5</version>
<scope>test</scope>
</dependency>
Steps class
With this in place, I can start the implementation. Usually, I place them in a class ending in Steps. In this
case src/test/java/io/terrafino/ac/cucumber/steps/StaticDataTestSteps.java
.
At first, some foundations.
package io.terrafino.ac.cucumber.steps;
import com.google.common.base.Strings;
import cucumber.api.java.After;
import cucumber.api.java.Before;
import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import io.terrafino.api.ac.AcException;
import io.terrafino.api.ac.ado.Ado;
import io.terrafino.api.ac.service.AcConnection;
import io.terrafino.api.ac.service.AcService;
import io.terrafino.api.ac.value.Value;
import java.util.Optional;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
public class StaticDataTestSteps {
private static AcConnection conn;
private static AcService ac;
private Ado ado;
private Value value;
@Before
public void before() throws AcException {
if (conn == null) {
conn = AcConnection.getDefaultConnection();
ac = new AcService(conn);
}
}
@After
public void after() throws AcException {
if (Optional.ofNullable(ado).isPresent()) {
ado.delete();
ado = null;
}
}
}
A quick breakdown:
- I need an AcConnection to connect to AC. And an AcService for ADO creation (lines 23-24).
- I also need a reference to my test ADO. And a place to keep the derived value (lines 26-27).
- There is a method to run before each scenario. This will create a connection to AC and initialise the AcService with it. As I have not found the equivalent of @BeforeClass for Cucumber tests, both the AcConnection and the AcService are static. And I check to see if conn is null, in order to open the connection only once.
- The after method will delete the test ADO after each scenario.
- I accept the fact that I am not explicitly closing the connection to AC at the end as I cannot tell when the last scenario has been run.
With this, I can look at implementing the individual steps:
@Given("^an Ado with prefix (\\S+)$")
public void anAdoWithPrefix(String prefix) throws AcException {
ado = ac.testAdoWithPrefix(prefix)
.withTemplate("BB_LSA")
.createInAc();
}
@And("^we set (\\S+) to (\\S*)$")
public void weSetAttrToValue(String attr, String value) throws AcException {
if (!Strings.isNullOrEmpty(value)) {
ado.setAndStore(attr, value);
}
}
@When("^we read the value of (\\S+)$")
public void weReadTheValueOf(String attr) throws AcException {
value = ado.load(attr);
}
@Then("^we get (\\S+)$")
public void weGet(String expectedValue) {
assertThat(value.toString(), is(expectedValue));
}
The methods are annotated with Given, And, When, Then, each followed by a regular expression matching the step clause. Parameters in the feature file are matched against regular expression groups and reflected in the parameters of the implementing method.
You can see the code to create the test ADO, set its attribute values and read them. And you see the assert statement that constitutes the test in the last method.
Executing the test
Manual run
To execute the test, I can open the feature file and (depending on the position of the cursor at the time)
can either run the whole feature file or a single scenario by selecting Ctrl-Shift-R
(Mac) or
Ctrl-Shift-F10
(Windows).
If this is your first feature file and steps class, chances are that IntelliJ can link the two together to be able to run them and the result should be as shown below:
In case you see error messages mentioning Undefined step, open the run configuration and ensure that the property Glue points to the package containing the steps class:
Implementing a runner
While binding feature files and steps classes via a run config and running the test manually is convenient during development, I also need a way to persist this in code.
Here is an example of how to do that:
package io.terrafino.ac.cucumber.steps;
import org.junit.runner.RunWith;
import cucumber.api.CucumberOptions;
import cucumber.api.junit.Cucumber;
@RunWith(Cucumber.class)
@CucumberOptions(
features = "src/test/features/bloomberg/"
,glue={"io.terrafino.ac.cucumber.steps"}
)
public class BloombergFeatureTest {
}
Now, I can just run BloombergFeatureTest which has the correct configuration of where to find the feature file(s) and steps implementation. I can also configure Maven Failsafe to pick up *FeatureTest classes and run them for me.
Adding more tests
Individual scenarios
I can now add more scenarios to my feature file, e.g.:
Scenario: BB ADO with Ado Type I and Product B
Given an Ado with prefix BB
And we set BB_FLW_ADO_TYPE to I
And we set BB_FLW_PRODUCT to B
When we read the value of BB.TEMPLATE
Then we get BB+COM_OP_LSA
Scenario: BB ADO with Ado Type I and Product C
Given an Ado with prefix BB
And we set BB_FLW_ADO_TYPE to I
And we set BB_FLW_PRODUCT to C
When we read the value of BB.TEMPLATE
Then we get BB+GCGC_LSA
But this is very verbose and if I want to fully test the underlying formula BB.TEMPLATE_FRM, I will end up with a rather large and hard to maintain feature file.
Using a Scenario Outline
For repeating scenarios, in which the Given/When/Then clauses are the same and only the actual values vary, Cucumber offers so-called scenario outlines. Here is what this looks like:
Scenario Outline: BB ADO derives correct template
Given an Ado with prefix BB
And we set BB_RDR_FILENAME to <filename>
And we set BB_ST006 to <actionType>
And we set BB_FLW_ADO_TYPE to <adoType>
And we set BB_FLW_PRODUCT to <product>
When we read the value of BB.TEMPLATE
Then we get <template>
Examples:
| adoType | product | filename | actionType | template |
| I | A | | | BB+COM_LSA |
| I | B | | | BB+COM_OP_LSA |
| I | C | | | BB+GCGC_LSA |
| I | D | | | BB+GCGC_LSA |
| I | E | | | BB+CUR_LSA |
| I | F | | | BB+EQY_OUT_LSA |
| I | G | | | BB+MF_LSA |
| I | H | | | BB+EQ_OP_LSA |
| I | I | | | BB+WAR_LSA |
| I | J | | | BB+GCGC_LSA |
| I | K | | | BB+MGT_AP_LSA |
| I | M | | | BB+GCGC_LSA |
| I | N | | | BB+EQY_OUT_LSA |
| I | O | | | BB+MGT_NP_LSA |
| I | P | | | BB+SYN_LSA |
| I | Q | | | BB+GCGC_LSA |
| I | S | | | BB+GCGC_LSA |
| I | T | | | BB+MGT_NP_LSA |
| I | U | | | BB+INDX_LSA |
| I | V | | | BB+MUNI_LSA |
| I | X | | | BB+MMKT_LSA |
| I | W | | | BB+GCGC_LSA |
| I | | mifid | | BB+MIFID_LSA |
| I | | bb_other | | BB+PERSEC_LSA |
| IS | | | | BB+INST_LSA |
| C | | | DELIST | BB+EQY_DELIST_LSA |
| C | | | LIST | BB+EQY_LIST_LSA |
| C | | | OTHER | BB+OTHER_LSA |
| H | | | | BB+PERSEC_LSA |
Here is what is different:
- Instead of the keyword
Scenario
I useScenario Outline
. - In the Given/When/Then clauses I use
<placeholders>
instead of specific values. - This is followed by
Examples
and a data table to provide the values. One column per placeholder. - Each line in the data table equates to one scenario.
This is a more concise way of expressing the test. And much easier to read and maintain.
I hope you found that useful. Thank you for reading.
In the next article I will look at a Cucumber test for Timeseries Validations.