Article
Test Driven Development: Are You Test-infected?
TDD In Action
Let’s look at a simple example of how one would use TDD with the SimpleTest unit test framework in PHP. I’m afraid you’ll have to use your imagination here because this example is obviously incredibly minimal in comparison to the large-scale applications on which TDD is usually employed. It’s beyond the scope of this article to tackle a larger problem. This article is not intended to be a beginner’s introduction to TDD—there are plenty of other articles floating around the Internet for that. Here, we’ll take the example of a basic filter chain, which we’d like to use to generate hyperlinks within text and to filter out naughty words. You can download the code for the example in this article, along with the tests.
Our interface might look something like this:
...
/**
* Performs a single filtering method on some input text.
*/
interface TextFilter {
/** Process $text and return a filtered value */
public function filter($text);
}
/**
* Performs filtering on text using a series of filters.
*/
class TextFilterChain {
/** Add a new filter to this chain */
public function addFilter(TextFilter $filter) {
}
/** Pass $text through all filters and return the filtered value */
public function filter($text) {
}
}
...
We’d like an AutoHyperlinkFilter and a NaughtyWordFilter, so we pick one and create merely the skeleton code for it:
...
class NaughtyWordFilter implements TextFilter {
public function filter($text) {
}
}
...
Then we create a test case. A test case is a single class. Typically, you have one test case for each concrete class in your system. The “thing” that the test case is testing is, as we saw above, often referred to as the SUT (the system under test). Any method inside the class, which begins with the word “test”, will be executed and reported upon. We expect a set of naughty words to be replaced here:
...
class NaughtyWordFilterTest extends UnitTestCase {
public function testNaughtyWordsAreReplaced() {
$filter = $this->_createFilter(array('foo', 'bar'));
$this->assertEqual(
"smurf! There's no way I'm doing that smurf...",
$filter->filter("foo! There's no way I'm doing that bar...")
);
}
private function _createFilter($words = array()) {
return new NaughtyWordFilter($words);
}
}
...
Now we run it:
...
$test = new NaughtyWordFilterTest();
$test->run(new TextReporter());
...
The test fails because we haven’t implemented our NaughtyWordFilter yet. This is good because the failing test is our signal to go ahead and write some code. It’s a specification, if you like—a target to hit.
The failure looks something like this:
NaughtyWordFilterTest.php
1) Equal expectation fails at character 0 with [smurf! There's no way I'm doing that smurf...] and [] at [/Users/chris/word_filter/tests/unit/NaughtyWordFilterTest.php line 11]
in testNaughtyWordsAreReplaced
in NaughtyWordFilterTest
FAILURES!!!
Test cases run: 1/1, Passes: 0, Failures: 1, Exceptions: 0
All we do is implement enough code to make this test pass:
...
class NaughtyWordFilter implements TextFilter {
private $_badWords = array();
public function __construct($badWords = array()) {
$this->_badWords = $badWords;
}
public function filter($text) {
foreach ($this->_badWords as $badWord) {
$text = str_replace($badWord, 'smurf', $text);
}
return $text;
}
}
...
Now that this test is passing, we see a less intimidating output when we run the test:
NaughtyWordFilterTest.php
OK
Test cases run: 1/1, Passes: 1, Failures: 0, Exceptions: 0
Now we can move onto our next class, AutoHyperlinkFilter. Here’s the skeleton code:
...
class AutoHyperlinkFilter implements TextFilter {
public function filter($text) {
}
}
...
We write a failing test next. We expect URLs to be turned into hyperlinks:
...
class AutoHyperlinkFilterTest extends UnitTestCase {
public function testURLsAreHyperlinked() {
$filter = $this->_createFilter();
$this->assertEqual(
'Go to my web site at <a href="http://site.com/">http://site.com/</a> and see!',
$filter->filter('Go to my web site at http://site.com/ and see!')
);
}
private function _createFilter() {
return new AutoHyperlinkFilter();
}
}
...
Implementation is next:
...
class AutoHyperlinkFilter implements TextFilter {
public function filter($text) {
return preg_replace('~(http://\S+)~i', '<a href="$1">$1</a>', $text);
}
}
...
We work through all of our required components, writing really short, concise tests for the behaviour of each component until we’re finished.