Behat: a quick tour 26/03/2014
TL;DR: jump to the conclusion.
This article is part of a series on Tests in general and on how to practice them:
- Introduction
- Tools overview
- Test Driven Development
- TDD: just do it!
- spec BDD
- phpspec: a quick tour
- Behavior Driven Development: story BDD
- Behat: a quick tour
- Conclusion
Story Behavior Driven Development (BDD) is all about making user story's acceptance criteria executable. In this article we'll oversee Behat a PHP framework which enables you to do so.
Introduction
In a nutshel Behat reads your user stories and links each steps in acceptance criteria to a function. The execution of those functions will then ascertain if the acceptance criteria succeeded.
To be able to read the user story, Behat needs you to write it in a specific format.
User story
By default, your user stories are located as follow: /features/*.feature
.
They're written using the Gherkin language,
which looks like this:
Feature: <user story title>
In order to <business value to attain>
As a/an <actor>
I need to <requirements to meet>
Scenario: <acceptance criteria title>
Given <precondition>
When <event>
Then <outcome>
The first lines will be printed while executing the acceptance criteria. The
Scenario
keyword starts a new criteria. The Given
, When
and Then
keywords will trigger a search for a related test method. Those are called
steps.
Your criteria will most likely have more than three lines. You can use And
and
But
keywords to link the steps:
Feature: <user story title>
In order to <business value to attain>
As a/an <actor>
I need to <requirements to meet>
Scenario: <acceptance criteria title>
Given <precondition>
And <another precondition>
When <event>
But <another event>
Then <outcome>
And <another outcom>
But <yet another outcome>
Note: to be fair, Given
, When
, Then
, But
and And
keywords aren't
different to Behat: the choice is there for you, in order to make your
acceptance criteria more readable.
Context
The test methods should be placed in a context: /features/bootstrap/FeatureContext.php
.
It looks like this:
<?php
use Behat\Behat\Context\BehatContext;
class FeatureContext extends BehatContext
{
/**
* @Given /^a sentence from an acceptance criteria$/
*/
public function aTestMethod()
{
// Your test code.
}
}
When Behat reads your user stories, for each step it will look in your context
and check the test method's annotations (comments starting by @Given
, @When
or @Then
) to see if it matches.
Note: again, @Given
, @When
and @Then
don't really matter. If you write
Given I am an imp
in your user story, and then write a test method with the
annotation @When /^I am an imp$/
, it will match!
As you can see, a regexp is used for the matching, but since the version 3.0 (still in release candidate at the time I write this article) you can use plain text with placeholders:
/**
* @Given I am an/a :type
*/
public function setType($type)
{
// Your test code.
}
This has been borrowed from Turnip.
Note: your test method name can be anything, it doesn't have to match the step sentence.
The definition of success
When a test method is executed, it can have the following state:
- not found: you need to create it
- pending: the test method exists, but isn't implemented
- failing: the method throws an exception or raises an error
- succeeds: the default
To set the pending state, write the following in your method:
throw new \Behat\Behat\Tester\Exception\Pending();
As you can see, if you write the test method, but put nothing in it, then the test will succeeds. The responsibility to make the success state match business expectations is yours.
Behat eats its own dog food: its tests are written with itself! Which means you can have a look at them to inspire yourself. You'll see something that isn't written in the documentation: you can use PHPUnit's assertion methods to make your test pass or fail.
An automated flow
Remember how phpspec generates your code based on your specifications? Well it's the same thing with Behat.
First Bootstrap your context:
behat --init
Write a /features/<user-story>.feature
file.
Next run the tests. For the pending steps, behat will propose you a template code which can be copy/pasted in your test methods:
behat
Then complete your test methods.
And finally run your tests:
behat
The tests should all fail. Which means now you can start writting the code to make it pass: it's Behavior Driven Development, remember? ;)
Misconceptions
A lot of people hate Behat because it's slow and it needs Selenium to work, which isn't easy to install (if a novice can't install it, then it's not easy). Oh, and they hate it because the tests written with it aren't maintenable.
Guess what? They're wrong. They're probably using the mink extension, which enables you to write things like:
Feature: User registration
In order to gain access to the website
As a user
I need to register
Scenario: Giving account details
Given I fill the field "#username" with "John"
And I fill the field "#password" with "Doe"
When I submit the form "ul.form-block > li:last > #submit"
And I wait until the page is fully loaded
Then I should see "You've registered successfully"
The thing is, you're not describing the business value in this acceptance criteria. You're describing User Interface (UI) interractions. And it's completly different!
So here's my rule of thumb: don't use mink nor selenium. In Silex, an implementation detail, the advice given is: imagine you need to add a CLI which shares the same functionnalities than the web interface. It would be a shame to have to re-write all your acceptance tests, wouldn't it?
Conclusion
Behat enables you to make your acceptance criteria executable, and automates the process. Awsome!
If you're using Selenium, or the mink extension, then you're doing it wrong: don't test the UI, test the business value.
Here's my workflow advice:
- write only one criteria
- implement only one step
- write the specification of one class used in the step implementation (using phpspec)
- write the code matching the specification
- go back to 3. until any code from the step implementation is written
- go back to 2. until any step is written
- go back to 1. until the user story is completely written
I hope you enjoyed this article, be sure to tweet me what you think about it ;) .
Story BDD and Behat have a steep learning curve, which makes them hard to practice in the beginning (but totally worth it). To help you get your own way, here's a list of references:
- Behat documentation
- Behat sources
- Behat version 3.0 announcement (slides)
- Behat version 3.0 announcement (video)
And of course have a look at the references from my BDD article.