GDPR Cookie Consent by Free Privacy Policy Generator
post thumb
Test Automation
by Matthias Hanitzsch-Moonlight/ on 26 Mar 2018

Cucumber - Derived Attribute

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:

Cucumber - Derived Attribute

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:

Cucumber - Run configuration

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 use Scenario 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.

comments powered by Disqus