http://www.developer.com/http://www.developer.com/lang/php/doing-behavior-driven-development-in-php-with-behat.html
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. 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: 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! 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 Next, run the You can implement step definitions for undefined steps with these snippets: Paste the three generated steps into a file named After creating the Behat is telling us that it's time to begin implementing the Because you'll need to use this With the Run Continue in this fashion, completing the Run Behat again and you'll see that all three tests have passed: 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!
Doing Behavior Driven Development in PHP with Behat
December 22, 2010
Installing Behat
%>pear channel-discover pear.everzet.com
%>pear install everzet/behat-betaWriting Your First Behat Test
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"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$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();
});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.phpusername.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.068sAccount 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;
}
}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
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);
});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.080sWhen 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);
});...
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