Tuesday, November 22, 2022

Behaviour-Driven Development Example

Behaviour-Driven Development (BDD) is a way of closing the gap between business (non-technical) people and technical people. Whereas Test-Driven Development (TDD) is testing at a component level, BDD is testing the behaviour at application level. In TDD, the test asserts the result of a specific method, while the BDD test is only concerned about the result of a high level scenario. Both are similar in way that the developer writes the test before writing the code.

Another way of saying this is business people will understand the BDD tests more easily than the unit tests. You'll understand this line better after this example code.

Assumptions

  • At least Java 8 installed
  • At least Maven 3.3.1 installed
  • Nice to have an IDE (e.g. IntelliJ)

Gherkin

In this example, we will be using the Cucumber JVM tool to do BDD. First off, we need to write a scenario in Gherkin. Gherkin is a set of rules that makes structured plain text for Cucumber to understand.


Scenario: Doxycycline order - HIV
	Given A customer orders Doxycycline
	And Suffers the condition HIV
	Then Reply should be Cannot prescribe

Scenario: Doxycycline order - None
	Given A customer orders Doxycycline
	And Suffers the condition, None
	Then Reply should be Can prescribe

As you can see, the above is clearly understandable to non-technical people. If you work in the medical software field, the above scenario tests are clearly understandable to clinicians checking if the software is working as it should.

Each scenario is a list of steps for Cucumber to work through. Cucumber verifies that the software conforms with the specification and generates a report indicating success or failure for each scenario. Apart from automating the testing, we have also documented how the system actually behaves. This is saved as a .feature text file.

Step Definition

We need to wire up the Gherkin steps to programming code. A step definition carries out the action that should be performed by the step. We'll use the cucumber-archetype Maven plugin to create a new project. Run the following command:


mvn archetype:generate                        \
   "-DarchetypeGroupId=io.cucumber"           \
   "-DarchetypeArtifactId=cucumber-archetype" \
   "-DarchetypeVersion=7.5.0"                 \
   "-DgroupId=bddexample"                     \
   "-DartifactId=bddexample"                  \
   "-Dpackage=bddexample"                     \
   "-Dversion=1.0.0-SNAPSHOT"                 \
   "-DinteractiveMode=false"

You should see something like below:


[INFO] Project created from Archetype in dir: C:\workspace\bddexample
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Change into the directory you created and open it in your IDE. Check if all is on order by running mvn test. You should see something like below:


[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.172 s - in bddexample.RunCucumberTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

Plus, you should have some kick-off code ready:

  • RunCucumberTest.java
  • StepDefinitions.java
  • example.feature

Write the Scenarios

Open up example.feature and change the scenarios as mentioned above. You should have something like below:


Feature: Doxycycline orders

	Scenario: Has HIV
		Given A customer orders Doxycycline
		And Suffers the condition "HIV"
		Then Reply should be "Cannot prescribe"
	
	Scenario: Condition is None
		Given A customer orders Doxycycline
		And Suffers the condition, "None"
		Then Reply should be "Can prescribe"

As for the StepDefinitions.java, change it as per below. This wires the steps to methods.


public class StepDefinitions {

    @Given("A customer orders Doxycycline")
    public void orderDoxycycline() {
    }

    @And("Suffers the condition {string}")
    public void suffersCondition(String condition) {
    }

    @Then("Reply should be {string}")
    public void replyIs(String expectedAnswer) {
    }

}

Business Logic

The next thing to do is create the business logic. We'll just do a simple class that checks for the condition and tell us if the order should go through. Add the code below under src/main/java, bddexample package.


package bddexample;

public class DoxyOrder {

	private String condition;
	
	public void setCondition(String condition) {
		this.condition = condition;
	}
	
	public String getPrescribeMessage() {
		if (condition.equals("HIV")) {
			return "Cannot prescribe";
		}
		
		return "Can prescribe";
	}
}

Let's put some meat in the empty step definitions.


package bddexample;

import io.cucumber.java.en.*;

import static org.junit.jupiter.api.Assertions.*;

public class StepDefinitions {
	
	private DoxyOrder order;

    @Given("A customer orders Doxycycline")
    public void orderDoxycycline() {
    	order = new DoxyOrder();
    }

    @And("Suffers the condition {string}")
    public void suffersCondition(String condition) {
    	order.setCondition(condition);
    }

    @Then("Reply should be {string}")
    public void replyIs(String expectedAnswer) {
    	assertEquals(expectedAnswer, order.getPrescribeMessage());
    }

}

Run the tests again and you should see something like below. As you can see below, it shows how many tests were ran and the steps that were undertaken. Clearly, it is understandable to the non-technical people that this piece of software is behaving as it should just by looking at the test scenarios that were performed.


[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running bddexample.RunCucumberTest

Scenario: Has HIV                         # bddexample/example.feature:3
  Given A customer orders Doxycycline     # bddexample.StepDefinitions.orderDoxycycline()
  And Suffers the condition "HIV"         # bddexample.StepDefinitions.suffersCondition(java.lang.String)
  Then Reply should be "Cannot prescribe" # bddexample.StepDefinitions.replyIs(java.lang.String)

Scenario: Condition is None            # bddexample/example.feature:8
  Given A customer orders Doxycycline  # bddexample.StepDefinitions.orderDoxycycline()
  And Suffers the condition "None"     # bddexample.StepDefinitions.suffersCondition(java.lang.String)
  Then Reply should be "Can prescribe" # bddexample.StepDefinitions.replyIs(java.lang.String)

... snipped ...

[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.368 s - in bddexample.RunCucumberTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

There you have it. A simple BDD example. The complete project can be cloned from github.com/jpllosa/bddexample.