Skip to content

Quickstart

Christoph Keiner edited this page Mar 13, 2019 · 6 revisions

Quickstart

In the following guide, you will learn how to use Test-Bddy to write BDD-style tests.

Outline:


Test-Bddy is a framework, which writes BDD-style acceptance tests directly as code. Hence, there is no separation between feature files and step definition. For this purpose, the keywords of the ubiquitous language Gherkin are repurposed as methods.

To start using Test-Bddy, download the framework and add it as a dependency to a project. To use Test-Bddy with Maven, add the following lines to the pom.xml and replace the version with the current one:

<repositories>
    <repository>
        <id>xceptance-releases</id>
        <url>https://lab.xceptance.de/nexus/content/repositories/releases/</url>
    </repository>
</repositories>
<dependencies>
    <dependency>
        <groupId>com.xceptance</groupId>
        <artifactId>test-bddy</artifactId>
        <version>${test.bddy.version}</version>
    </dependency>
</dependencies>

Then create a class, i.e. DefineFeature.

Test-Bddy separates the definition of a BDD component (feature, scenario, step) from their execution. Following is a description of how to define the components.

A step depicts the smallest component in a BDD test case. It consists of a description and a behaviour. In case of Test-Bddy, the initial step is a static method defined inside BddSuite. Every Gherkin keyword for a step except But is translated to a method, i.e. given, when, then, and. They each expect a description as first parameter. The second argument is a Runnable and depicts the step definition/behavior.

given("Define a given step", () ->
{
    // The step definition/behavior as code, for example
    System.out.println("Given I define some step");
})

Each of these step-methods returns a Steps object. It also has these methods, but they are non-static. Hence, to add additional steps, they are appended with the dot notation as such:

given("Define a given step", () ->
{
    System.out.println("Given I define some step");
})
.when("Define a when step", ) ->
{
    System.out.println("I define another step with the dot notation");
})

Again, a Steps-object is returned with every step-method. This allows to chain each step to the previous one.

A scenario describes one acceptance criterion to a feature. In other words, it describes exactly one test case. Normally, the test has a name and multiple steps to verify the criterion. In case of Test-Bddy, scenario is the method to define one. It is static and also located in BddSuite. The first argument is the name and the second argument a Steps-object. Since the latter can be created with a static step-method, a scenario is defined as follows:

scenario("Define a Scenario",
    given("Define a given step", () ->
    {
        System.out.println("Given I define some step");
    })
);

Each feature consists of several acceptance criterion. Thus one feature has several scenarios. In addition, it should have name. Test-Bddy uses the static feature-method defined in BddSuite to this end. The first argument is the name of the feature and the second argument an array of suppliers of scenarios. The latter parameter is a varargs. This means that each element of the array can also be defined as additional parameter. Furthermore, a short-handed supplier can be used, since a scenario-definition only requires a single statement. Hence, a feature with only one scenario looks like the following:

feature("Define a feature",
    () -> scenario("Define a Scenario",
            given("Define a given step", () ->
            {
                System.out.println("Given I define some step");
            })
        )
);

And a feature with two scenarios as follows:

feature("Define a feature",
    () -> scenario("Define a Scenario",
            given("Define a given step", () ->
            {
               ...
            })
        ),
    () -> scenario("Define another Scenario",
            when("A test without preconditions is needed", () ->
            {
            	...
            })
        )
);

A fully defined feature can be executed by calling its test-method. This executes each scenario and thus the test for each acceptance criterion.

An example with all required imports looks as follows:

import static com.ckeiner.testbddy.api.BddSuite.feature;
import static com.ckeiner.testbddy.api.BddSuite.given;
import static com.ckeiner.testbddy.api.BddSuite.scenario;
import org.junit.Test;

public class DefineFeature
{
    @Test
    public void defineAFeature()
    {
        feature("Define a feature",
            () -> scenario("Define a scenario",
                    given("Define a given step", () ->
                    {
                        System.out.println("Given I define some step");
                    })
                    .when("Define a when step", () ->
                    {
                        System.out.println("I define a when step");
                    })
                    .then("Define a then step", () ->
                    {
                        System.out.println("I should define a then step");
                    })
                )
        ).test();
    }
}

The test-method does actually more than execute each lower-level component. It also verifies whether the component should or can be run and possibly sets up a report.

BDD uses concrete examples to illustrate scenarios. Such scenarios are then called scenario outline. Test-Bddy also supports these with the scenario-method. However, for such a data-driven test the second argument has to be of the type OutlineDescriptor.

An OutlineDescriptor is created with the static with-method in BddSuite. It expects a varargs of type T, which is the data type of the concrete examples. Each parameter then depicts exactly one test datum and the scenario outline is run once for each test datum.

The OutlineDescriptor returned by withData also supplies the step-methods. However, the step-methods now either expect a Runnable or Consumer as second argument. The latter's input parameter has the type T and as such of the test data. During the execution, the steps are executed once for each test datum which is used as input for the Consumer. Hence, in case of the following definition, the parameter of the Consumer first is firstDatumand then during the second execution secondDatum.

with(Object firstDatum, Object secondDatum)

Since the input parameter of a Consumer is the only way to access the test datum, it must be used if the data needs to be accessed. Otherwise, a Runnable should be used to indicate that no test datum is needed for this step.

A full example and its output is shown below:

feature("Define a feature",
    () -> scenario("Define a scenario",
            with("Custom Output")
            .given("Define a given step", () ->
            {
                System.out.println("Given I define some step");
            })
            .when("Define a when step", data ->
            {
                System.out.println("I define " + data);
            })
            .then("Define a then step", () ->
            {
                System.out.println("I should see my own data");
            })
    )
).test();

The console output of the test looks like this:

[] Feature: Define a feature
================
ScenarioOutline: Define a scenario
================
Using testdata:
Custom Output
Define a given step
Given I define some step
Define a when step
I define Custom Output
Define a then step
I should see my own data

Every error and exception is caught by Test-Bddy and bundled in a MultipleFailureException which is contained within an error or exception. Thus, the JUnit output in case of a failure looks opaque. Additionally, it only shows whether the surrounding method of the feature passed. Hence, it may be unknown whether a scenario passed. To not require a runner and be dependent on a testing framework like JUnit or TestNG, the reporting framework ExtentReports is employed. During the execution, it receives information about whether a component passed, what its status is, etc. The report is then generated at <test-bddy>/report/extent.html.

An example is found here.