Sitemap

Making Behat Useful

4 min readFeb 16, 2023

--

Using BDD to drive the design of your domain

The Behat logo.
Behat is a tool for automating behavioural testing, as part of the BDD process

BDD is a frequently misunderstood concept. Too often, the term BDD is used to refer to integration tests which happen to be running through Behat. Many developers find that their Behat tests add little value to their development process.

Used properly, Behat can be a tool to translate requirements into a well architected domain. BDD isn’t a kind of test; it’s a process that begins with requirements gathering, and ends with a domain that works. This article examines how to gather requirements that can be translated into good Behat tests, and how good Behat tests design good software. We will study an example, to see how the BDD process blends requirements gathering, test-first development, and software architecture.

If you are a developer already using Behat, but your tests aren’t helping you as much as they could, this post is for you.

Gathering Requirements

When gathering requirements from a stakeholder, it isn’t enough simply to use the familiar Given/When/Then format. What you need is objectively testable examples. Here are two examples. The second is more helpful to us than the first.

Example 1 — an unhelpful scenario

Given a customer chosen a product

When we calculate the tax

Then VAT should be 20%

Example 2 — an objectively testable example

Given a customer’s order contains a chocolate bar with a total price of £1.00

When we calculate the tax on the order

Then the VAT should be £0.20

The second example leaves less room for ambiguity. It clarifies, for example, that the stakeholder meant 20% of the total, not 20% of the base price. Crucially, a QA could use this as an actual test case, because it is an example, not a rule.

Press enter or click to view image in full size
A man and a woman having a meeting in an office. A thought bubble represents the idea they are discussing.
BDD is a process that starts with requirements gathering

Writing The Step Definition

This is the crucial part, where the domain is designed. The main value of using a behavioural testing tool is in the design process that happens here.

Bear in mind the concept of ubiquitous language. The language your stakeholders used during requirements gathering should be reflected in the test scenario, and in your class and method names. The code should speak the same language as the business.

Get Sam Burns’s stories in your inbox

Join Medium for free to get updates from this writer.

Example: a step definition for part of our scenario

Press enter or click to view image in full size
A code snippet showing the Behat step definition for a line of the scenario. The code in the step definition instantiates objects called ‘Product’ and ‘CustomersOrder’. The step definition also invokes a method called ‘contains’ on the ‘CustomersOrder’ object.

Note that we have kept the ubiquitous language of terms like ‘customer’s order’ and ‘contains’, and reflected this language in the actual class and method names. The relationship between products and customers’ orders is implied by the natural language from the requirements gathering process. This relationship is reflected in the object graph. In true Behaviour Driven Development, the requirements have driven the design of our code.

Using TDD to Implement Our Objects

This section will be brief, as the process of writing unit tests is out of scope for this post, but note the following observations:

  • We have referred to a class called Product in our step definitions, but it doesn’t exist yet.
  • We will, at least for testing purposes, need to be able to instantiate Product using a named constructor static factory function called fromNameAndPrice().
  • We have referred to a class called CustomersOrder, but it doesn’t exist yet.
  • We will need a method called contains() on our CustomersOrder class.

Test Driven Development (using a conventional unit testing tool) should now be used to create these objects. Other step definitions will require more such methods on these classes.

Summary

We now have proper behavioural testing. This is what we have done:

Build the domain

We have built the domain for this feature. It is decoupled from application and infrastructure. It is portable. It is objectively testable, and the tests prove the domain works for all our examples.

This is what we have not done:

Build the application

The application layer (i.e. controllers, command objects etc.) have not been mentioned at all. We can literally build our domain without knowing if we are building a website, a CLI application, or an API. HTTP has not been mentioned in our tests.

Build the infrastructure

We can build our entire domain without needing to know what the infrastructure is. Just as our behavioural tests should still pass if we delete all the controllers, they should all pass if we delete the database as well.

Press enter or click to view image in full size
Three small islands in the sea, representing the decoupling of the domain, application, and infrastructure
The Domain, Application, and Infrastructure are completely decoupled. Our Behat tests only test the domain. Integration tests elsewhere can provide coverage of the other two

Just as the behavioural tests for our domain should still pass if we delete all the controllers, they should all pass if we delete the database as well

Overall, our Behat tests have designed our domain for us. The scenarios don’t mention HTTP, they don’t mention a database, and they don’t mention a user interface. Our domain is aggressively decoupled from these concepts, and therefore so are its tests. Good Behat scenarios talk about the domain, in a language borrowed from stakeholders, and drive our software architecture.

The structure of an application’s domain emerges as we write our Behat tests. For this reason, they have been extremely useful to us. As long-living tests, describing what the stakeholder wanted, they will help defend this functionality from bugs in the future.

--

--