Making Behat Useful
Using BDD to drive the design of your domain
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.
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
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.
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.





