I had the occasion to try out and adopt Behat for a particular need in our projects at Onebip. Here is my recount of the experience from a PHPUnit contributor and invested person.
There are many tehnical tests for which I think Behat is not the right fit:
- unit tests that cover single objects.
- Integration tests that verify Facades built over external resources such as a MongoDB collection, MySQL table, or the filesystem.
- Cross-browser tests where it is verified a new skin for the payment page is applied, or that the user interface does not have regressions on IE 8.
- Acceptance tests at the level of a single application, that verify the relation between HTTP requests and responses plus their side-effect on the system.
- End-to-end tests that put more than one application together, and follow a workflow such as the user performing a payment by inserting a pin or via the 1-click flow.
The context where I find Behat to be the perfect fit instead is even more high-level than the acceptance and end-to-end tests I just described: specifying (and automatically verifying) business rules more important than the user interface steps.
To do this, Behat steps exercise an automation layer that goes under the skin of the Facade application (at the HTTP API level) and verify the external interactions of several Bounded Contexts working together:
- which billings are performed on the user's phone
- how frequently they are billed during subscriptions
- which messages they receive.
These business rules are very important as they are what we're selling to merchants, our real customer (with respect to the user); and they are verified at the highest possible level, stubbing only the mobile operator and merchant side with fake HTTP servers to avoid spending real money.
When the need for specifying business rules in a business-readable format, we initially started out by writing these tests in PHPUnit, following the Given-When-Then format. However, adopting Gherkin and Behat was one of those cases where everyone else in the world is telling you to switch, yet you only see the benefit of this choice after the fact. For my personal opinion on the matter, the book specification by Example helped me in understanding the goals of this approach:
- making specifications not only automatable, but concise and readable by colleagues that do not write code.
- separating the automation layer (written in PHP and exposed to Behat FeatureContext object) from the specification one (written in Gherkin, a stricter form of plain text).
- Reducing the telephone game between between business and developers: we want to get to the point where an Offer Team member and a developer can write together feature files as part of the conversation related to a user story.
- Build a living documentation system that internal users can access to know what's currently working and what the live system is supposed to do.
These goals, however, require an investment in automation that partly we already made (for end-to-end technical tests) and part we are making now. What was missing from the existing automation layer? While single HTTP interactions with the API were covered, the missing parts regard being able to hide irrelevant details. For example:
- we don't want to perform the verification of the user via sending a PIN and entering it in the user interface in every specification. If we are specifying the billing frequency, this is a detail that can be hidden in a single 'I subscribed to $service with a pin flow' step; even when the text of the PIN message or the number of external interactions is variable and complex.
- We don't want to specify how and if operators notify us of payment, each with its own protocol.
- We don't want to build phone numbers by ourselves, we just want to pick one of a particular mobile operator.
The technical side
It is really important to not let Behat's FeatureContext class grow, and to continuously extract objects for automation purposes. Here are some examples of what we extracted:
- A Client object performing HTTP requests and responses, and producing and parsing JSON objects while providing sane defaults during the creation phase.
- A PhonenumberGenerator object creating fixture phone numbers belonging to particular operators and countries.
- A SimulatedUser object able to wait for an SMS message to be received and showing what has been received instead in case of timeout.
- (ready to be extracted but not yet refactored) Workflow objects representing different payment procedures, from inserting a PIN to making a single click to sending an SMS.
It has been the first time in my life I found myself abstracting away the test framework: this has been definitely the case where composition wins over inheritance as the growing PHPUnit test cases had been built from the start by delegating to Plain Old PHP Objects that we can now easily port to the Behat environment.
I must say Behat is a very modern project, much improved and organized with respect to the version I tried years ago, when you had to define steps in a procedural script as anonymous functions.
Composer support is present and the user interface is as friendly and Poka-yoke as possible. If you are facing complex business rules in a PHP application, and the need for improving communication between who writes a user story and who implements it, consider this as one of the tools of the trade.