Methods & Tools Software Development Magazine

Software Development Magazine - Programming, Software Testing, Project Management, Jobs

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

  1. http://www.phpunit.de
  2. http://behat.org/
  3. http://cukes.info/
  4. http://osherove.com/tdd-kata-1/
  5. 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