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.