Writing Good BDD Scenarios

Behat and other behavioural testing tools take, as their input, a feature file with scenarios.  These scenarios, in the Gherkin format, allow for a (non-technical) human-readable expression of the requirements of a feature.  The way we write Behat scenarios has come a long way in the seven year history of the tool, so here is a brief look at how we used to write Gherkin scenarios, how they are written now, and what the advantages are of a more modern approach.

Behat Scenarios in Days Gone By

Once upon a time, a typical Behat scenario used to look like this:

Scenario:

Given I am on ‘/shop/product?id=123’
When I click on the ‘Add to basket’ button
And I wait 2 seconds
Then I should see ‘Laptop’ in the table of products

This scenario contains frequent references to the user interface.  It describes elements on the page (‘button’, ‘table of products’, …), refers directly to a URL, and even contains a step specifying a time delay.  The browser control tool being used, likely Selenium, is being explicitly told here to wait for AJAX to load before continuing with the test.  The scenario doesn’t really further our understanding of the business domain, and its implementation is likely silent on the subject of software architecture.

Under the bonnet, this scenario is implemented using step definitions in Behat.  They will most likely be tightly coupled to things like CSS classes, implicit prerequisites in database fixtures, and the exact wording used in any CMS or translation tool.

In addition to failing at the slightest sign of a front-end change, this UI test will be quite flakey and will take more than its fair share of time to execute, as it is being run through a browser, using real HTTP queries and a real database.

Behat and Service Level Testing

The above scenario could be reimplemented as a service level test, without change to the wording.  ‘Given I am on [url]’ would have to be interpreted here as merely identifying a particular product.  “I should see ‘Laptop’ in the table of products” could be implemented using something along the lines of ‘$basket->getProducts()’, and the step implying there is a time delay could be ignored completely.  A service level test will run faster, and is inherently more reliable.  Classes called ‘BasketDataAccessObject’ and ‘InMemoryBasket’ could turn up, implementing the same interface, but allowing a Behat test to use a fake DAO which simply stores its contents in an in-memory array, improving the speed of the testing considerably.

This does not, however, mean that the scenario itself drives good software design; nor would it mean much to a non-technical stakeholder.  Its value in confirming that we have correctly understood requirements is limited.

Behat Scenarios and Modelling by Example

Here is another, more modern, way of writing a BDD scenario:

Background:

Given there is a product called ‘Laptop’ with the SKU 123456
And SKU 123456 is priced at £250

Scenario:

Given I am a customer
And I have nothing in my basket
When I add SKU 123456 to my basket
Then I should have one product in my basket
And the base order value should be £250

This scenario uses Ubiquitous Language: the language used by (non-technical) domain experts to talk about the business and its processes.  It provides a much richer insight into the domain.

The scenario models the business process described using Modelling by Example (MbE), meaning that a realistic example is used to express the process the application supports.  Suddenly, we can ask the right questions about the domain: do SKUs ever have letters in?  Are there any customers who would see different pricing?  Apart from the base order value, what else is there about an order?

The scenario is completely decoupled from the user interface.  Although it could be used for a UI test if necessary, it lends itself much more readily to driving the programming of a good domain.  Step definitions will now start to look like this:

$product = new Product(
    Sku::fromInt(123456),
    Price::fromString('£250'),
    'Laptop'
);

[...]

$testResult = $basket
    ->getBaseOrderValue()
    ->equals(Price::fromString('£250'));

This gives us real information about the domain we have to build – guiding us towards classnames that make sense to future developers working closely with non-technical colleagues.  Emerging business requirements in future will come at us in a way that inherently makes sense when looking at the code.  Our constructors, and later our database columns, will be the right shape for real data.

Summary

Overall, Behat has been an important tool at the heart of good PHP development over the past seven years.  With Behaviour Driven Development and Modelling by Example, the scenarios we write and the way we gather requirements has changed.  Writing a good Behat scenario, and doing so during conversation with domain experts, has many advantages: the conversation you have yields real insight and understanding; the scenarios drive good DDD, and help you build the right thing right; and the examples are objectively testable.  Writing a good Gherkin scenario is now more important than ever in guiding the development process.