Developers often use unit tests to determine code coverage. Many times, unit testing is abused to perform integration testing rather than to verify a single functional unit (a function with no side effects) works as expected.
Adopting Behavior Driven Development testing patterns verifies that an application feature reliably reproduces expected business behaviors.
What is Behavior Driven Development?
Behavior Driven Development (BDD) is an agile process which describes the desired functionality of an application as a set of behaviors.
A behavior is how the system or application responds to a given stimulus or input.
BDD describes behaviors through specifications which include Narrative and Acceptance Criteria.
Narrative
The Narrative is a short story that is a common part of defining the Stories used in Agile/Scrum to relate between developers and stake holders what is being built.
The Narrative typically follows the structure:
- As a person or role
- I want to do or have thing
- So that benefit or value achieved
Acceptance Criteria
The Acceptance Criteria for a Behavior is Scenario driven. Each Scenario describes a preset condition and action along with the expected outcome.
The Acceptance Criteria follows the structure:
- Scenario title describing the overall conditions and expected outcome
- Given the initial conditions of the situation
- When an action is taken, or event occurs
- Then an expected outcome occurs
Why use BDD in testing?
Aligning the test scenarios with the business goals of the application through a common Domain Specific Language (DSL) clarifies to all stakeholders what that software is achieving.
Keeping the goals of the software in a common DSL understood by both the software and business leadership teams creates more accurate communication. This communication allows all stakeholders to determine whether the application under development is achieving the required business goals.
Consider the following test cases first described as unit tests and then described as a Scenario:
Unit Test Example
In the above unit tests, we are checking the path of storing an Applicant data object, determining if the Applicant object evaluates correctly as valid (where valid may mean several things), and if the Applicant is promoted to a Customer data object as three (3) separate disjointed tests.
BDD Test Example
Scenario: Registering a Safe and Valid Applicant as a Customer
Given a person submits a customer application
When the application is reviewed
And the applicant is confirmed as safe and valid
Then the application is registered as a customer in the system
In the above scenario, we are describing the process of a person moving from potential customer to customer.
The question that quickly arises from the example: where’s the code?
BDD testing tools: Cucumber
There are many BDD testing tools which focus on transforming the Scenario script into executable code.
One of the more popular available cross-platform frameworks is Cucumber.
The following demonstrates using Cucumber with Java and Spring Boot.
Project Dependencies
In a Spring Boot project, include the Cucumber BOM within your managed dependencies.
The following example assumes maven as the built tool:
With the BOM in-place, we can add the following test scope dependencies:
Configuration
To execute Cucumber feature files, we need to define:
- The directory under resources which contains the *.feature files describing the Scenarios
- The directory which contains the *Steps.java files to execute
- A configuration file which provides the “glue” tying feature files to executable code
Feature File Directory
A feature file describes the scenarios associated with a given feature of the application or system.
In the test/resources directory of the spring boot project, create a directory named “features” which will contain our feature file tests.
Step File Directory
Each Scenario is a collection of Steps which follow the given, when, then format. The code associated with these steps is placed in a “steps” directory by convention.
In the src/main/test/java/{com.your.package} directory, create a new directory named “steps”. This will contain the “*Steps.java” files referenced at runtime by Cucumber to perform the steps of the feature Scenarios.
Configuration Files
The Configuration for Cucumber will use two files: a TestConfig.java and a CucumberConfig.java file.
The TestConfig.java is the normal Spring Boot test config file used for Junit testing:
Note that this is a standard configuration with the following annotations:
- Configuration – indicates that the file is meant to configure the module
- ContextConfiguration – points to config classes in the module under test if the module does not contain a ‘main’ class.
- TestPropertySource – points to application.properties file in the src/main/test/resources directory which performs any special application configuration for test purposes only.
The CucumberConfig.java is used to configure the glue between the *.feature files and the *Steps.java files.
Note that the Cucumber configuration extends the TestConfig class to include it with the test configuration.
The annotations are as follows:
- Suite – marks this configuration as using the Junit test runner.
- CucumberContextConfiguration – marks this configuration as a Cucumber context configuration.
- IncludeEngines – include the Cucumber engine during testing.
- SelectClasspathResources – annotation that specifies where under the resources directory to find the feature files.
That gives Junit enough information to allow testing the feature files.
Feature Definitions
Feature files define the Scenarios which describe how the feature (for which the file should be named) should behave under the given conditions.
In the “features” directory, a feature file named CustomerRegistry.feature may have a Scenario describing a successful customer registration process using a KYC service (a financial industries term for Know Your Customer).
The Scenario describes the following conditions:
- Given a User applies to be a customer for the service using information that is valid for the Know Your Customer process …
- When that KYC process evaluates the information (known to be valid) for the applicant …
- Then the service will upgrade the applicant User to a customer User in the system …
- And a customer checking account will be created
The Scenario creates a compound test of ensuring that a single condition (successful application with valid KYC information) produces a series of expected results (upgrading the User to a Customer and creating a checking account for the customer to use).
To evaluate these expected behaviors, we need to create a CustomerRegistrySteps.java file in the steps directory.
Steps Definition
Create a CustomerRegistrySteps.java class file in the src/main/test/java/{com.your.package}/steps directory of your project.
In this file we will include annotations for each step of the scenario:
In the above class definition, we have done the following:
- Added a @Given annotation to set the initial state of our system
- Added a @When annotation to indicate that an action is performed on the system
- Added a @Then annotation to verify the outcome of the action
- Added a @And annotation to validate additional results as necessary
The file itself makes use of helper classes and services that are specific to the system under test. The importance here is demonstrating that custom code is used to simulate the states of the scenario at each step and execute those steps in code to get a result.
The result is then evaluated to determine if our system achieves the desired behaviors when the given states and conditions are applied.
A win-win for business and software development teams
Behavior Driven Development testing aligns business and developer goals by providing a common language in which to describe both success and failure conditions of an application.
Using a Given-When-Then approach to describing scenarios gives common context and understanding to what is expected of the software under development.
Describing behaviors and expected outcomes with a common understanding between business and development teams mitigates the risk of misunderstandings to delay or prevent feature development.