The Wayback Machine - https://web.archive.org/web/20111026230642/http://www.developer.com/lang/php/doing-behavior-driven-development-in-php-with-behat.html
October 26, 2011
RSS RSS feed

Doing Behavior Driven Development in PHP with Behat

A few weeks ago I made the trek to Cleveland to attend the fantastic Cleveland Ruby Brigade meeting, which happens to be held at one of the coolest user group gathering spots in the country. Jeff Morgan spoke on the topic of Cucumber, a behavior driven development tool that allows developers to write software tests in plain English (and more than 20 other spoken languages). This opens up a world of possibilities for further involving non-programming members of any project in the development process, not to mention making the adoption of a test-first approach to software development even more compelling.

Although I regularly use Ruby on a variety of projects, the majority of my time is spent working with PHP. So, I wondered whether Cucumber was limited solely to the Ruby and Rails community. Further investigation indicates that Cucumber can in fact be used in conjunction with all mainstream languages, among them Java, Python, and you guessed it, PHP. Check out the Cucumber wiki for a complete listing of supported languages and related notes.

This investigation also turned up a native PHP BDD solution named Behat. Behat works in a manner quite similar to Cucumber, including using the very same spoken language test syntax (incidentally known as Gherkin). Although still a beta and not yet as capable as Cucumber, Behat provides PHP developers with the ability to take advantage of BDD using the familiar PHP environment and syntax.

Installing Behat

Behat takes advantage of several PHP 5.3.1-specific features (among them anonymous functions and namespaces), so you'll need to run PHP 5.3.1 or newer. When you have confirmed this setup, install Behat by adding its PEAR channel and installing the project:

%>pear channel-discover pear.everzet.com
%>pear install everzet/behat-beta

You can further enhance Behat's capabilities using PHPUnit's assertion features. So, in order to follow along with the examples provided in this tutorial, be sure to install PHPUnit. See my PHPBuilder.com article Use PHPUnit to Implement Unit Testing in Your PHP Development for more information.

When installed, you're ready to begin doing BDD with Behat!

Writing Your First Behat Test

The Behat wiki defines Gherkin as a "business readable, domain specific language that lets you describe the software's behavior without detailing how that behavior is implemented." You'll use Gherkin to describe application features and the corresponding expected behavior. Behat will use regular expressions to translate these natural language descriptions into PHP code, which is used to actually test the application, with the added bonus of generating much of this PHP code for you!

This approach makes it easy-- and even fun--to begin testing your application models. Consider an application that allows users to create an account and requires that the username consist of at least six characters. We can use Behat to create a scenario that spells out this requirement. Place the following feature and corresponding scenario into a file named username.feature, saving it to a directory named features within your project's root directory:

# language: en
Feature: Valid Username
As a website user
In order to create an account
The username must consist of at least six characters

Scenario: Provide valid username
Given I provide "jasong" as a username
When I retrieve the username
Then the username should be set to "jasong"

Next, run the behat command from within your project's root directory. Doing so will result in Behat parsing the feature file and generating the PHP code that will be used to test your application:

%>behat
Feature: Valid Username
As a website user
In order to create an account
The username must consist of at least six characters

Scenario: Provide valid username # features/username.feature:7
Given I provide "jasong" as a username
When I retrieve the username
Then the username should be set to "jasong"

1 scenario (1 undefined)
3 steps (3 undefined)
0.069s

You can implement step definitions for undefined steps with these snippets:

$steps->Given('/^I provide "([^"]*)" as a username$/', 
function($world, $arg1) {
throw new EverzetBehatExceptionPending();
});

$steps->When('/^I retrieve the username$/', function($world) {
throw new EverzetBehatExceptionPending();
});

$steps->Then('/^the username should be set to "([^"]*)"$/',
function($world, $arg1) {
throw new EverzetBehatExceptionPending();
});

Paste the three generated steps into a file named username.php, and save this file to a directory named steps, which resides in the features directory. At this stage, your application directory will look like this:

features/
username.feature
steps/
username.php

After creating the username.php file, run Behat again and you'll see a different outcome:

Feature: Valid Username
As a website user
In order to create an account
The username must consist of at least six characters

Scenario: Provide valid username # features/username.feature:7
Given I provide "jasong" as a username # features/steps/username.php:5
TODO: write pending definition
When I retrieve the username # features/steps/username.php:9
Then the username should be set to "jasong" # features/steps/username.php:13

1 scenario (1 pending)
3 steps (2 skipped, 1 pending)
0.068s

Behat is telling us that it's time to begin implementing the Account model. Although the best practice is to first complete the test before writing any code, for the purposes of this exercise I want to provide you with a mental picture of what the Account model class looks like. So, nd create a class named Account.php and place it within a directory named models:

class Account {

private $_username;

public function setUsername($username)
{
if (strlen($username) < 6)
{
throw new Exception("username is not valid");
} else
{
$this->_username = $username;
}
}

public function getUsername()
{
return $this->_username;
}

}

Because you'll need to use this Account class and additionally various PHPUnit features within the tests, create a bootstrap file named env.php, saving it to a directory called support within the features directory and adding the following contents:

account = new Account();

Implementing Your Behat Test

With the Account model and bootstrap file in place, you can begin implementing the test, starting with the Given step. In this step we will put the system into a known state, assigning the username via the setUsername() method:

$steps->Given('/^I provide "([^"]*)" as a username$/', function($world, $arg1) {
$world->account->setUsername($arg1);
});

Run behat again and you'll see that one test is now passing:

...
Scenario: Provide valid username # features/username.feature:7
Given I provide "jasong" as a username # features/steps/username.php:5
When I retrieve the username # features/steps/username.php:9
TODO: write pending definition
Then the username should be set to "jasong" # features/steps/username.php:13

1 scenario (1 pending)
3 steps (1 passed, 1 skipped, 1 pending)
0.080s

Continue in this fashion, completing the When and Then steps as demonstrated here:

$steps->Given('/^I provide "([^"]*)" as a username$/', function($world, $arg1) {
$world->account->setUsername($arg1);
});

$steps->When('/^I retrieve the username$/', function($world) {
$world->username = $world->account->getUsername();
});

$steps->Then('/^the username should be set to "([^"]*)"$/', function($world, $arg1) {
assertEquals($world->username, $arg1);
});

Run Behat again and you'll see that all three tests have passed:

...
Scenario: Provide valid username # features/username.feature:7
Given I provide "jasong" as a username # features/steps/username.php:5
When I retrieve the username # features/steps/username.php:9
Then the username should be set to "jasong" # features/steps/username.php:13

1 scenario (1 passed)
3 steps (3 passed)
0.130s

Admittedly, this is an exceedingly simple example, but it nonetheless provides you with a clear understanding of Behat's functionality. Be sure to check out the Behat website and GitHub project page for more details!


Tags: PHP, BDD



Networking Solutions
Sitemap | Contact Us
X