Prev | Next |
In [Astels2006], Dave Astels makes the following points:
Extreme Programming originally had the rule to test everything that could possibly break.
Now, however, the practice of testing in Extreme Programming has evolved into Test-Driven Development (see Chapter 13).
But the tools still force developers to think in terms of tests and assertions instead of specifications.
So if it's not about testing, what's it about? It's about figuring out what you are trying to do before you run off half-cocked to try to do it. You write a specification that nails down a small aspect of behaviour in a concise, unambiguous, and executable form. It's that simple. Does that mean you write tests? No. It means you write specifications of what your code will have to do. It means you specify the behaviour of your code ahead of time. But not far ahead of time. In fact, just before you write the code is best because that's when you have as much information at hand as you will up to that point. Like well done TDD, you work in tiny increments... specifying one small aspect of behaviour at a time, then implementing it. When you realize that it's all about specifying behaviour and not writing tests, your point of view shifts. Suddenly the idea of having a Test class for each of your production classes is ridiculously limiting. And the thought of testing each of your methods with its own test method (in a 1-1 relationship) will be laughable. | ||
--Dave Astels |
The focus of Behaviour-Driven Development is "the language and interactions used in the process of software development. Behavior-driven developers use their native language in combination with the ubiquitous language of Domain-Driven Design to describe the purpose and benefit of their code. This allows the developers to focus on why the code should be created, rather than the technical details, and minimizes translation between the technical language in which the code is written and the domain language spoken by the" domain experts.
The PHPUnit_Extensions_Story_TestCase
class adds a story
framework that faciliates the definition of a
Domain-Specific Language
for Behaviour-Driven Development. Inside a scenario,
given()
, when()
, and
then()
each represent a step.
and()
is the same kind as the previous step. The following
methods are declared abstract
in
PHPUnit_Extensions_Story_TestCase
and need to be
implemented:
runGiven(&$world, $action, $arguments)
...
runWhen(&$world, $action, $arguments)
...
runThen(&$world, $action, $arguments)
...
In this section, we will look at the example of a class that calculates the score for a game of bowling. The rules for this are as follows:
The game consists of 10 frames.
In each frame the player has two opportunities to knock down 10 pins.
The score for a frame is the total number of pins knocked down, plus bonuses for strikes and spares.
A spare is when the player knocks down all 10 pins in two tries.
The bonus for that frame is the number of pins knocked down by the next roll.
A strike is when the player knocks down all 10 pins on his first try.
The bonus for that frame is the value of the next two balls rolled.
Example 14.1
shows how the above rules can be written down as specification scenarios
using PHPUnit_Extensions_Story_TestCase
.
Example 14.1: Specification for the BowlingGame class
<?php
require_once 'PHPUnit/Extensions/Story/TestCase.php';
require_once 'BowlingGame.php';
class BowlingGameSpec extends PHPUnit_Extensions_Story_TestCase
{
/**
* @scenario
*/
public function scoreForGutterGameIs0()
{
$this->given('New game')
->then('Score should be', 0);
}
/**
* @scenario
*/
public function scoreForAllOnesIs20()
{
$this->given('New game')
->when('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->and('Player rolls', 1)
->then('Score should be', 20);
}
/**
* @scenario
*/
public function scoreForOneSpareAnd3Is16()
{
$this->given('New game')
->when('Player rolls', 5)
->and('Player rolls', 5)
->and('Player rolls', 3)
->then('Score should be', 16);
}
/**
* @scenario
*/
public function scoreForOneStrikeAnd3And4Is24()
{
$this->given('New game')
->when('Player rolls', 10)
->and('Player rolls', 3)
->and('Player rolls', 4)
->then('Score should be', 24);
}
/**
* @scenario
*/
public function scoreForPerfectGameIs300()
{
$this->given('New game')
->when('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->and('Player rolls', 10)
->then('Score should be', 300);
}
public function runGiven(&$world, $action, $arguments)
{
switch($action) {
case 'New game': {
$world['game'] = new BowlingGame;
$world['rolls'] = 0;
}
break;
default: {
return $this->notImplemented($action);
}
}
}
public function runWhen(&$world, $action, $arguments)
{
switch($action) {
case 'Player rolls': {
$world['game']->roll($arguments[0]);
$world['rolls']++;
}
break;
default: {
return $this->notImplemented($action);
}
}
}
public function runThen(&$world, $action, $arguments)
{
switch($action) {
case 'Score should be': {
for ($i = $world['rolls']; $i < 20; $i++) {
$world['game']->roll(0);
}
$this->assertEquals($arguments[0], $world['game']->score());
}
break;
default: {
return $this->notImplemented($action);
}
}
}
}
?>
phpunit --story BowlingGameSpec
PHPUnit 3.4.2 by Sebastian Bergmann.
BowlingGameSpec
[x] Score for gutter game is 0
Given New game
Then Score should be 0
[x] Score for all ones is 20
Given New game
When Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
and Player rolls 1
Then Score should be 20
[x] Score for one spare and 3 is 16
Given New game
When Player rolls 5
and Player rolls 5
and Player rolls 3
Then Score should be 16
[x] Score for one strike and 3 and 4 is 24
Given New game
When Player rolls 10
and Player rolls 3
and Player rolls 4
Then Score should be 24
[x] Score for perfect game is 300
Given New game
When Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
and Player rolls 10
Then Score should be 300
Scenarios: 5, Failed: 0, Skipped: 0, Incomplete: 0.
Prev | Next |
assertArrayHasKey()
assertClassHasAttribute()
assertClassHasStaticAttribute()
assertContains()
assertContainsOnly()
assertEqualXMLStructure()
assertEquals()
assertFalse()
assertFileEquals()
assertFileExists()
assertGreaterThan()
assertGreaterThanOrEqual()
assertLessThan()
assertLessThanOrEqual()
assertNotNull()
assertObjectHasAttribute()
assertRegExp()
assertSame()
assertSelectCount()
assertSelectEquals()
assertSelectRegExp()
assertStringEndsWith()
assertStringEqualsFile()
assertStringStartsWith()
assertTag()
assertThat()
assertTrue()
assertType()
assertXmlFileEqualsXmlFile()
assertXmlStringEqualsXmlFile()
assertXmlStringEqualsXmlString()
Copyright © 2005-2009 Sebastian Bergmann.