Software Development Magazine - Project Management, Programming, Software Testing |
Scrum Expert - Articles, tools, videos, news and other resources on Agile, Scrum and Kanban |
Behat: The BDD library for PHP
Gonzalo Ayuso, @gonzalo123, http://gonzalo123.com/
Behat is an open source Behavior Driven Development (BDD) framework for PHP inspired by the Ruby Cucumber BDD framework.
Web Site: http://behat.org/
Version tested: 2.4.*@stable
System requirements: PHP>=5.3
License & Pricing: Open Source, MIT license, free
Support: Website, Community
What’s BDD? Simple question. BDD is an acronym of: Behavior Driven Development (maybe I prefer to use "Design", instead of "Development"). If we want to speak about BDD, first we need to speak about TDD (Test-Driven Development/Design). Maybe you are familiarized with TDD. TDD is a Software development process. The idea of TDD is simple. When we want to write code, first we write one failing test (Red). Then we write the code to pass this failing test and finally our test passes (Green). After several Red-Green cycles we can Refactor (we only refactor on green). The motto of test-driven development is "Red, Green, Refactor.".
OK. More or less TDD is simple. There’re are several Tools in PHP and probably the most important one is PHPUnit. [1] PHPUnit is the "standard" tool for unit testing in PHP, indeed. But this article is about BDD, What’s the difference between TDD and BDD?. According to two of the most important actors in the Development scenario, Robert C. Martin and Kent Beck (notice that I stand up when I speak about those two people) there isn’t a simple answer. Robert C. Martin (a.k.a. "Uncle Bob", the writer or the new developer Bible "The Clean Code") "BDD is just TDD". By the other hand Kent Beck (one of the founders of the Extreme Programming (XP) and the Agile Manifesto), thinks that TDD is one part of BDD. Without TDD we cannot work with TDD. BDD extends TDD to one higher level
A little mess, isn’t it? In my humble opinion BDD is the same idea but maybe it can be use for a broader audience. It isn’t only focussed on developers. With BDD, managers, and business owner can play an important part within the way of design software.
Too much philosophy, isn’t it? Let’s play with something more concrete and let’s speak about the subject in question: Behat. [2] Behat is Behavior Driven Development framework for PHP applications inspired in a famous Ruby’s tool called Cucumber. [3] The idea of Cucumber (and Behat) is to create human readable sentences to describe our application. Notice the "human readable". That means that everybody can read the features of our application. No matter if the reader is a developer or a business owner. Everybody can read it and contribute. To do that Behat implements a tool called Gherkin. Gherkin provides us a natural language, easy to understand and communicate with the client but it’s also well defined and it’s easily automated with software tools. Do you want an example? A good one can be taken from the Behat’s project page:
Scenario: Given I am in a directory "test" And I have a file named "foo" And I have a file named "bar" When I run "ls" Then I should get: """ bar foo """
This code is just a specification of a program. As you can see is readable, and with a glance you can figure out how it works. But it isn’t only a simple documentation. It uses Gherkin, and Gherkin can be interpreted by a software (Behat in our case). That’s means that this piece of text "compiles" and we can use it to test our application. It’s an agreement that our program must fulfill. Interesting, isn’t it?
With Behat we define "Features". Each feature has its file with the extension ".feature". Features are different points that our program must fulfill. In each feature file we define different Scenarios. The example above is just one scenario from our program. Scenarios have Steps. In our example we have five steps. It’s pretty straightforward. But if you are reading this article, probably you are a developer and developers prefer code than boring text, so let’s create an example from scratch using Behat. In this example we are going to develop String Calculator [4] Coding Kata (at least the first steps). If you are not familiarized with Coding Kata, it is a term borrowed from martial arts. They are simple exercises that help us to improve our skills. The aim of a Kata is not to solve the exercise that is normally very simple. The aim is to grow as a developer solving the exercise again and again, using different approaches and perspectives within each iteration.
OK. Having said that let’s start with our String Calculator. First we prepare our environment. We are going to use composer to perform this task. So let’s include the Behat library in our composer.json file as it is well documented in Behat’s project page:
{ "require":{ "behat/behat":"2.4.*@stable" }, "minimum-stability":"dev", "config":{ "bin-dir":"bin/" }, "autoload":{ "psr-0":{ "":"lib/" } } }
We execute "composer install" and Behat is installed. Now we are going to initialize Behat within our project:
./bin/behat --init +d features - place your *.feature files here +d features/bootstrap - place bootstrap scripts and static files here +f features/bootstrap/FeatureContext.php - place your feature related code here
This script creates the file structure needed to run Behat. So now we are going to create our first feature in "features/StringCalculator.feature". This feature will have our first scenario. Just the first step of the kata: "Add an empty string returns 0"
Feature: String Calculator Scenario: Add an empty string returns 0 Given StringCalculator instance When I Add "" Then I expect it to return 0
Now we can run Behat to check our application:
./bin/behat Feature: String Calculator Scenario: Add an empty string returns zero # features/StringCalculator.feature:3 Given StringCalculator instance When I Add "" Then I expect it to return zero 1 scenario (1 undefined) 3 steps (3 undefined) 0m0.048s
You can implement step definitions for undefined steps with these snippets:
/** * @Given /^StringCalculator instance$/ */ public function stringcalculatorInstance() { throw new PendingException(); } /** * @When /^I Add "([^"]*)"$/ */ public function iAdd($arg1) { throw new PendingException(); } /** * @Then /^I expect it to return (\d+)$/ */ public function iExpectItToReturn($arg1) { throw new PendingException(); }
Confused? Maybe the first time but if we read the output of the script we will realize that it’s very simple. We have written our first feature file with the specification, but Behat is not a wizard. It won’t write the code for us and it also doesn’t know how to check if our application works according to our feature file. But at least it helps us with the process. We only need to copy the hints that Behat throws us in bootstrap/FeatureContext.php. Now we run again Behat and:
./bin/behat Feature: String Calculator Scenario: Add an empty string returns zero # features/StringCalculator.feature:3 Given StringCalculator instance # FeatureContext::stringcalculatorInstance() TODO: write pending definition When I Add "" # FeatureContext::iAdd() Then I expect it to return zero # FeatureContext::iExpectItToReturnZero() 1 scenario (1 pending) 3 steps (2 skipped, 1 pending) 0m0.047s
Now is different. Behat tell us something like: "I notice that there’s one scenario with three steps, but it fails." That means that we need to define those steps. Let’s start with the first one. Now we replace the original stringcalculatorInstance function with:
private $stringCalculator; /** * @Given /^StringCalculator instance$/ */ public function stringcalculatorInstance() { $this->stringCalculator = new StringCalculator(); }
We also need to create the StringCalculator class. We create it in the lib directory.
<?php class StringCalculator{}
Now we can run behat again and …
./bin/behat ... 1 scenario (1 pending) 3 steps (1 passed, 1 skipped, 1 pending) 0m0.054s First step is green. Good!. Let’s go with the second one: private $textToAdd; /** * @When /^I Add "([^"]*)"$/ */ public function iAdd($text) { $this->textToAdd = $text; }
And our behat execution:
./bin/behat ... 1 scenario (1 pending) 3 steps (2 passed, 1 pending) 0m0.048s
Now we are going to face the most important step within our scenario "Then I expect it to return 0". Behat doesn’t have any assertion library. We can use any library, but it fits perfectly with PHPUnit, so we only need to include PHPUnit in our composer.json file:
... "require":{ "behat/behat":"2.4.*@stable", "phpunit/phpunit": "3.7.13" }, ...
Now if we update our run "composer update" again and uncomment the PHPUnit include in our FeatureContext.php we can use assertions in the same way than we use them with PHPUnit
/** * @Then /^I expect it to return (\d+)$/ */ public function iExpectItToReturn($expected) { assertEquals($expected, $this->stringCalculator->add($this->textToAdd)); }
Now our Behat execution fails, (remember: red first), but we can fix it:
class StringCalculator { public function add($text) { return 0; } }
Now everything works:
./bin/behat Feature: String Calculator Scenario: Add an empty string returns zero # features/StringCalculator.feature:3 Given StringCalculator instance # FeatureContext::stringcalculatorInstance() When I Add "" # FeatureContext::iAdd() Then I expect it to return 0 # FeatureContext::iExpectItToReturn() 1 scenario (1 passed) 3 steps (3 passed) 0m0.056s
Our first feature works, let’s go to the second one. Now we are going to check "Add "1" returns 1". So we add another scenario to our feature file:
Scenario: Add "1" returns 1 Given StringCalculator instance When I Add "1" Then I expect it to return 1
As we can read our steps are the same than the first scenario, because of that if we run Behat again, it will not say "Hey I don’t know how to implement your steps". So we don’t need to change anything within our FeatureContext.php file, we only need to change the implementation of the StringCalculator to meet with the new requirements:
<?php class StringCalculator { public function add($text) { return $text == '' ? 0 : $text; } }
And that’s all. Now our Behat again:
./bin/behat Feature: String Calculator ... 2 scenarios (2 passed) 6 steps (6 passed) 0m0.065s
We can keep on with our kata adding new features and implementing the appropriate functionality. Remember: baby steps, first the failing the test, and then the code. As you can see I like to use BDD in the same way than TDD. I hope this post has awaken your curiosity about BDD and Behat, and I strongly encourage to finish the Kata. You can get this code from my github account [5]. And remember: Red, Green, Refactor...
References
- http://www.phpunit.de
- http://behat.org/
- http://cukes.info/
- http://osherove.com/tdd-kata-1/
- https://github.com/gonzalo123/stringCalculatorBehat
More Software Testing Resources
Click here to view the complete list of tools reviews
This article was originally published in the Spring 2013 issue of Methods & Tools
Methods & Tools Testmatick.com Software Testing Magazine The Scrum Expert |