What is glue code?

I am more and more interested in acceptance testing lately and if you browse through the posts of this blog you are going to find one or another post about the whole idea along with some noteworthy tooling.

One aspect I haven’t touched so far is how do we bring the actual tests and the system under test (SUT) together and if we use code, where should it reside?

Let us start with a small recap, so we all are on the same page and then move on to the sparking bits after that.

A small recap

The general idea of behavior-driven development is to consider a software component as a blackbox and describe (and later verify) the observable behavior without any knowledge of its internal workings or the chosen technology - this is in contrast to other tests, which follow a whitebox approach.

Naturally, a test-driven development approach works pretty well here, but there really is no requirement to this or even to be standing on a green field.

When you are sitting in front of a huge pile of shi code and are desperately trying to figure out what it should do exactly, it even morel beneficial to explore the behavior and document it - supported by actual examples.

So far the theory - example time!

Use cases

The following use cases are again based on a simple todo application with a REST endpoint to create and manage todo entries - an OpenAPI can be found here.

Use case #1: Create a todo with title and description

The creation is pretty straight forward, we just have to pass a title and a description over to our endpoint and verify, if we receive the id of a newly created entry.

If we rephrase this to the Three R format (coined by Connextra) used for stories in many agile teams, we end up with something like this:

As a user, I want to create a new todo entry with title and description, so that it is stored
with an unique ID for later retrieval.

Equipped with this, let us dive into how this can be done with Cucumber and Karate.

Cucumber

A Gherkin scenario intentionally resembles the Three R and can be easily translated into almost natural language:

Feature: Create a todo

  Scenario Outline: Create a todo with title and description and check the id.
    Given I create a todo (1)
    When its title is "<title>" (2)
    And its description is "<description>"
    Then its id should be <id> (3)

    Examples:
      | title  | description  | id |
      | title1 | description1 | 1  |
      | title2 | description2 | 2  |
1 This describes who wants to do something. (Role)
2 Next up is what is needed. (Requirements)
3 And finally why.. (Reason)

Calling Cucumber a simple pattern matcher probably doesn’t live up to it, but for all practical matters it is exactly that. (Trust me on that - you can replace each of given/when/then with a simple * and it still magically works)

There are many different language bindings available, but here we want to focus on the JVM one:

public class TodoSteps {
    private final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private RequestSpecification requestSpec;
    private TodoBase todoBase;
    private DueDate dueDate;

    @Before
    public void beforeScenario() {
        this.requestSpec = new RequestSpecBuilder()
                .setPort(8081)
                .setContentType(ContentType.JSON)
                .setAccept(ContentType.JSON)
                .build();
    }

    @Given("I create a todo") (1)
    public void given_create_todo() {
        this.todoBase = new TodoBase();
        this.dueDate = new DueDate();
    }

    @When("its title is {string}") (2)
    public void when_set_title(String title) {
        this.todoBase.setTitle(title);
    }

    @And("its description is {string}") (3)
    public void and_set_description(String description) {
        this.todoBase.setDescription(description);
    }

    @Then("its id should be {int}") (4)
    public void then_get_id(int id) {
        String location = given(this.requestSpec)
            .when()
                .body(this.todoBase)
                .post("/todo")
            .then()
                .statusCode(201)
            .and()
                .extract().header("location");

        assertThat(location.substring(location.lastIndexOf("/") + 1))
                .isEqualTo(Integer.toString(id));
    }
}
1 On a match create a new Todo object.
2 Set a title to our Todo object.
3 ..and also set a description.
4 And finally call the endpoint and assert whatever comes back.

There are no surprises here - so let’s see how this can be done in Karate.

Karate

Karate also relies on the Gherkin language and I am most certain you see similarities here. In contrast to the previous example with Cucumber we don’t have to write any Java code to get this running. Under the hood, Karate uses a full-fledged html engine and provides built-ins or rather special keywords for the actual tests:

Feature: Create a todo

  Background:
    * url 'http://localhost:8081' (1)

  Scenario Outline: Create a todo with title and description and check the id.
    Given path 'todo' (2)
    And request (3)
    """
    {
      "title": <title>,
      "description": <description>
    }
    """
    When method post (4)
    Then match header location ==  "#regex .*/todo/<id>" (5)

    Examples:
      | title    | description    | id |
      | 'title1' | 'description1' | 1  |
      | 'title2' | 'description2' | 2  |
1 Point the internal engine to the given url.
2 Update the path of the current location.
3 Define the actual request JSON body.
4 Set the HTTP method and fire the request.
5 And again - compare whatever comes back - here the header.

What about another example, that relies less on materials on board?

Use case #2: Create a todo with start and due date

Instead of sending a real request to our backend, we want to verify the internal logic of our domain object this time. Aforementioned logic here is, whenever we create a todo with a due date after the start date, it should automatically be marked as done. (Honestly I also think this is a really strange requirement, but sometimes it is like that.)

To get some practice here, let’s convert it to the Three R format as well:

As a user, I want to create a new todo entry with a start and a due date, so that it is
automatically marked as done, when the due date is after start, to avoid unaccomplishable tasks.

Cucumber

The actual translation from the Three R format to a scenario is quickly done:

  Scenario Outline: Create a todo with start and due dates and check the status.
    Given I create a todo
    When it starts on <start>
    And it ends on <due>
    Then it should be marked as <status>

    Examples:
      | start      | due        | status  |
      | 2021-09-10 | 2022-09-10 | undone  |
      | 2021-09-10 | 2021-09-09 | done    |
public class TodoSteps {
    private final DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");
    private RequestSpecification requestSpec;
    private TodoBase todoBase;
    private DueDate dueDate;

    @Before
    public void beforeScenario() {
        this.requestSpec = new RequestSpecBuilder()
                .setPort(8081)
                .setContentType(ContentType.JSON)
                .setAccept(ContentType.JSON)
                .build();
    }

    @Given("I create a todo")
    public void given_create_todo() {
        this.todoBase = new TodoBase();
        this.dueDate = new DueDate();
    }

    @When("it starts on {datestr}") (1)
    public void when_set_start_date(LocalDate startDate) {
        this.dueDate.setStart(startDate);
    }

    @And("it ends on {datestr}")
    public void and_set_due_date(LocalDate dueDate) {
        this.dueDate.setDue(dueDate);
    }

    @Then("it should be marked as {status}") (2)
    public void then_get_status(boolean status) {
        this.todoBase.setDueDate(this.dueDate);

        assertThat(status).isEqualTo(this.todoBase.getDone());
    }

    @ParameterType("[0-9]{4}-[0-9]{2}-[0-9]{2}") (3)
    public LocalDate datestr(String datestr) {
        return LocalDate.parse(datestr, this.dtf);
    }

    @ParameterType("done|undone")
    public boolean status(String status) {
        return "done".equalsIgnoreCase(status);
    }
}
1 Set start and due date.
2 And also set the status of the entry.
3 Convert different types.

Parameter types (or Transformers in older versions of Cucumber) can help to encapsulate and convey domain concepts like the status. A positive side effect is they also allow to adhere to the DRY principle by not repeating information - here how to convert input to a date. Apart from that, the second Cucumber example is basically the same all over again - this changes with with the next one.

Karate

This example is heavier on the glue code side and since we normally don’t use Java directly for Karate tests, we have to find a way to call Java directly. Fortunately, Karate DSL also provides helper here and we can implement the tests like we did before:

  Scenario Outline: Create a todo with start and due dates and check the status.
    Given def createTodo = (1)
    """
    function(args) {
      var TodoType = Java.type("dev.unexist.showcase.todo.domain.todo.Todo"); (2)
      var DueDateType = Java.type("dev.unexist.showcase.todo.domain.todo.DueDate");
      var DateTimeFormatterType = Java.type("java.time.format.DateTimeFormatter");
      var LocalDateType = Java.type("java.time.LocalDate");

      var dtf = DateTimeFormatterType.ofPattern("yyyy-MM-dd"); (3)

      var dueDate = new DueDateType();

      dueDate.setStart(LocalDateType.parse(args.startDate, dtf));
      dueDate.setDue(LocalDateType.parse(args.dueDate, dtf));

      var todo = new TodoType();

      todo.setDueDate(dueDate);

      return todo.getDone() ? "done" : "undone";
    }
    """
    When def result = call createTodo { startDate: <start>, dueDate: <due> } (4)
    Then match result == "<status>" (5)

    Examples:
      | start      | due        | status |
      | 2021-09-10 | 2022-09-10 | undone |
      | 2021-09-10 | 2021-09-09 | done   |
1 Define a function that executes the test.
2 Create a wrapper for our required Java classes.
3 Implement the actual test in the next few lines.
4 Call the previously defined method and pass the values from the table.
5 ..and verify the result.

Conclusion

What can we learn from both examples?

  • Glue code is generally all code, that is required to combine the actual test with the system under test and resides somewhere in between.

  • Cucumber separates the glue code from the actual features, whereas Karate includes them in the feature file.

  • Cucumber advises to keep technical details out of the features, Karate benefits the combination of both.

I think the real answer is somewhere in the middle - in my opinion separating the technical details from the actual specification is beneficial, because it becomes robust to changes of the underlying systems and doesn’t have to be touched whenever the implementation changes.

This also depends on the included functions and people:

  • When the business side defines the specifications and also writes the feature files less technical is easier to understand and favors focus on the development of a Ubiquitous Language for the business domain.

  • On the other hand, when dev writes the specifications, keeping both bundled together helps to avoid any drift between specification and the technical tests.

All examples can be found in my acceptance testing showcase at the usual place:

Bibliography

  • [legacycode] Michael C. Feathers, Working Effectively with Legacy Code, Pearson 2004