Introduction to Behavior Driven Development (BDD)

Introduction to Behavior Driven Development (BDD)

What is BDD?

BDD is a form of software testing that focuses on the behaviors of a software application as it relates to business functionality. This is a collaborative methodology that brings in a cross functional team to write the tests before the implementation code is written. BDD brings testing into the forefront of software development instead of an afterthought. It also invests multiple parties into becoming owners of these very important assets.

BDD does have some standard structures and formats. The tests themselves are written in natural looking markup language called Gherkin. Since the tests are written in a natural looking language many parties without coding skills can contribute to the test and understand what existing tests are saying. Most importantly a natural looking markup language is machine readable so the tests can be automated.

A BDD testing ideally covers a single application and focuses on the business requirements and outcomes.

An Overview of the BDD Process

Writing a BDD Test

BDD brings together developers, testers and business analysts to collaboratively write a feature file.

BDD is designed to fix the miscommunication of working and designing in silos.

Classic Misunderstanding

Role of the Developer

The primary role of the developer is to help guide the others as to how the testing framework functions and to make sure the feature file will be programmatically consumed best.

It also gives them an advanced heads up as to what code will need to be implemented in the near future. This should be the same developer that will write the implementation code

Role of the Business Analyst

This role knows the business needs and can describe the how the software should work and various business conditions.

Role of the Tester

This role understands testing and how and can think about all the various conditions that might be faced. Depending on staffing levels in each role I do like this role to be able to fill in the obvious things in the feature that don’t always need to be painstakingly described by the team as a whole.

Anytime someone touches a feature file alone, there should be a final sign offs by the team to ensure correctness on all three fronts. Checking feature files into source control especially after every meeting or individual effort is a great way to track changes and quickly see what changed.

BDD Testing Architecture

The basic components of the BDD testing architecture are:

BDD Architecture

Feature Files

Example of ReceiptTotaling.feature (shortened for brevity):

Feature: Receipt Totalling

Background:
Given a sales tax of 5% on all items

Scenario: Regular Sale of 2 Items
Given the following sold items:
  | Item  | Unit Quantity | Price |
  | Apple | 2 lbs         | $4.99 |
  | Milk  | 1 gallon      | $5.99 |
When the receipt is totalled
Then the following line items are shown on the receipt:
  | Item Description | Price |
  | Apples (2 lbs)   | $4.99 |
  | Milk (1 gallon)  | $5.99 |
And a subtotal of $10.89
And a sales tax of $0.54
And a total amount of $11.52

Scenario: Buy 2 Get 1 Free Sale
Given the following sold items:
  | Item           | Unit Quantity | Price |
  | Apple          | 2 lbs         | $4.99 |
  | Fizzy Pop Cola | 12 pack       | $8.99 |
  | Dr Fizzy Pop   | 12 pack       | $8.99 |
  | Fizzy Pop Mtn  | 12 pack       | $8.99 |
And a buy 2 get 1 free sale on any Fizzy Pop items
When the receipt is totalled
Then the following line items are shown on the receipt:
  | Item Description                     | Price  |
  | Apples (2 lbs)                       | $4.99  |
  | Fizzy Pop Cola (12 pack)             | $8.99  |
  | Dr Fizzy Pop (12 pack)               | $8.99  |
  | Fizzy Pop Mtn (12 pack)              | $8.99  |
  | Sale Buy 2 Get One Free on Fizzy Pop | -$8.99 |
And a subtotal of $27.97
And a sales tax of $1.39
And a total amount of $28.36

A BDD Framework

Cucumber is the most popular by far and comes with multiple languages. Head over to cucumber.io to which languages are currently implemented.

Theoretically you could create your own framework but I would generally recommend against it, Cucumber is free and open source. This will save you a ton of time to implement BDD.

Glue Code

Glue code or harness code ties the data and instructions in the feature file to code under test. Cucumber will call this code based on the feature file steps. This is code you will need to write, though cucumber can provide some assistance. Ideally glue code will setup all the data per scenario in order to run a test.

Regarding the programming language of the glue code if you have written your application in a language other than a supported cucumber language you can still use cucumber. You will need a generic form of integration such as an API to exercise the code under test. Depending on the application, this may be a special API just for the test code or maybe it already exists for the production release. This can be a good practice even if the language matches over direct code invocation.

For Web UI based testing there is a Selenium driver that helps automate feature file steps into UI actions from the glue code. Testing via UI has its pro and cons over an API and is a discussion that could go into great depths.

Target Application / Core Services Under Test

This is a version of the application the that is being developed for production release. Ideally this is a manageable sized application and setup that can be run on a developers machine / environment or a CI environment. If this setup is too large or unwieldy it will make BDD less of a benefit. Remember BDD is about functionality and isolating the functionality to test it. The quicker a test can run the faster you get feedback and the more often you will use it.

Databases Owned by the Applications Under Test

If your application under test uses a database you may optionally want us a database as part of the test. This has pros and cons as to if you should use a database for a BDD testing setup.

The biggest tip I can give you for success is the database or at least the test data in the db needs to be isolated for the test. Isolated means the test must have a database where it can function in both a reliable and in a repeatable way without manual setup of data or just happening to have the right data in place. Ideally you should be starting with an empty database and the glue code can populate the necessary generic data from templates or code checked into source control, then overlaid with the data that matters from the feature file. This should give you the ability to execute the test easily, quickly and consistently in multiple environments. If you get this right, test driven coding will be easy and vastly preferable.

Mock Services in Place of External Services

External services should be mocked, this has the following advantages:

  • Consistent responses so that predictable results can be explicitly defined in the Then section. Remember this is not meant to be a full integration test.
  • Smaller and simpler environments for rapid testing.
  • Isolation of the broader enterprise systems so that problems can be narrowed down quicker, especially in a continuous integration environment.

In your feature file you will want to define data in the Given section that the mock service will return. This is especially useful for testing when service responses are non determinate. As an example an applications consuming AI or search results might get different answers every day and it’s not clear what response at any given time will come out of the external system. Mocking gives you the ability to write the test and use predictable hypothetical responses from services so we can describe the expected result of the application under test using concrete values.

If you have a micro services architecture depending on the scope of the service and what makes sense for the scope of the feature file(s), you might in certain circumstances want to test a few closely related services integrated together.

Messaging or Eventing Broker

If you are using messaging or eventing, you will need to use mock consumers or producers to create messages / validate expected messages or events that are produced. You should probably use an actual matching broker but it should be isolated to the BDD testing environment.

Example Feature Development Flow Using BDD

Brief overview of an established BDD process:

  1. Using agile methodologies assign a bite sized story
  2. The cross functional team meets to discuss the feature and write scenarios.
  3. The developer adds any new glue code necessary to use the feature file in an automated manner.
  4. The developer runs just the added scenarios relevant to the story, they will likely fail.
  5. The developer writes code to make the new scenarios pass. Working in an iterative manner writing code to pass each new test.
  6. The developer runs existing features and scenarios to ensure they didn’t break the working code.
  7. The Developer commits and pushes the code changes
  8. CI run the tests when opening a pull / merge request to ensure they pass
  9. Code is ready for manual review

What BDD is Not

Some of the common misconceptions are:

It’s Not a Unit test

This is not a unit test, nor should it replace unit tests. Unit tests operate on a much smaller scale of code and are much more technical in nature. BDD operates at a business feature level and with good coding practices many units of an applications will be necessary to fulfil business needs.

It’s Not an API test

This is not meant to test the technical implementation of an API or if your API contract is correct. Depending on how you design the BDD testing environment you may inadvertently test an API in order to get the test to execute and get a proper response but it’s not the main goal of BDD.

It’s Not an Integration Test

This is not meant to make sure all of your systems talk together properly. You should be mocking between most broader systems.

A monolithic test that tests everything

BDD should not be viewed as a one-size fits all for all your testing needs. It is not a replacement for all aspects of software testing or as a complete replacement to typical legacy manual integrated testing. BDD may replace parts of an existing testing strategy. However, there are certain aspects of software testing, whether it is done as automated or manual that BDD will not cover and need to be addressed outside this approach. Much like how microservices is moving into many specialized deployments, testing should be viewed similarly. Certain types of tests are strong for certain things but not everything. Ideally a multiple type of testing strategy will lead to the highest quality code and automation of each type will lead to the fastest output.

What I like about BDD

  • Joint Ownership of testing between multiple parties. Everyone is more invested into the tests instead of just being relegated to a single owner.
  • Clear scoping of development based agile stories. Writing the test first as a team, it’s very clear what you are getting yourself into before you develop a story and it’s very clear when you are done (the tests passes). Hint: make passing certain defined tests the acceptance criteria of a development story.
  • Automation and reduction of toil. Automation frees you from monotonous chores of setting up data and going through manual procedures over and over again to see if something works. Development can be an exercise in trial and error until things work. Not only is automation faster and free of human error, it cognitively keeps you in a focused zone.
  • Speed, once the initial investment in getting BDD setup is done the results can be a dramatic increase in productivity.
  • Focused testing. With BDD your in the business feature zone and can really focus on the business requirements. You can more cleanly focus technical aspects, integration and innovation separately, which is also very important.
  • Failing fast CI integration, especially when coupled with branching / forking strategy can isolate which changes, if any, are causing issues very quickly.
  • Higher quality code through more frequent testing, this makes testing fast and cheap. When that happens you will test more often and find more errors before production releases.

Personally, I have seen this approach significantly improve software development efficiency over the standard SDLC process where testing is an afterthought of code development. It takes some time to implement but it is well worth it on greenfield projects or maybe even on a brownfield project.

Video