摘要
本文向你介绍测试驱动开发的概念,并用一个简单的示例项目来做示范。
介绍
开发PHP产品有很多不同的方法。我们大多数倾向于从一个简单的脚本开始,逐步向前推进。 或许我们可以预先列出我们的脚本,但是我们往往是停留在开发阶段,在需要测试的时候不会真正的去开始测试。基本上,我们是先开发后测试。
但是这样做或许不是最好的办法,可能会在今后带来问题。这就是为什么一些开发者提倡一种不同的开发方式,叫做测试驱动开发(TDD)的原因- 就是先测试后开发。
你可能会疑惑这样该怎么做,而这正是本文将讨论的。我将带领你们通过一个真实的简单项目去示范TDD如何工作。本文和示例项目都基于Noel Darlow(“McGruff”)在论坛中向另一个论坛成员演示TDD如何工作的讨论。
我们的示例项目是一个Biter类,它通过使用正则表达式可以“咬掉”字符串中的片段,就象这样:
bite ('/pattern/');?>
我们的类也将修改原始的字符串,把匹配的部分去除(所以我们叫它“吞噬者”)。
让我们从设置测试框架开始。
设置测试框架
由于我们从测试出发,我们需要有一些测试框架。我将使用SimpleTest 框架,仅仅因为我最熟悉它。
下载一份SimpleTest的拷贝,把它安装在你本机或者你的服务器上。然后创建一个叫做"test_biter.php"的新文件,里面写下面的代码:
require_once 'simpletest/unit_tester.php';
require_once 'simpletest/reporter.php';
class BiterTestCase extends UnitTestCase {
function testSetup () {
$this->assertTrue(false);
}
}
$test = new BiterTestCase('TDD Biter Test');
$test->run(new HtmlReporter());
?>
让我们分析一下这个例子。首先我们包含了一些SimpleTest框架的文件(你要确认一下路径是否和你的一样)。接着我们建立了一个叫做BiterTestCase的新类,它将用来测试我们的Biter类。象你看到的一样,BiterTestClase类继承于UnitTestCase类,这个意味着BiterTestClass是我们第一个真正意义上的测试用例。
BiterTestClass类只有一个方法调用叫做'testSetup'。任何以“test”开头的方法都会被SimpleTest框架自动执行,因此它们应该是被用来测试项目中的某个部分。在上面的例子中,我们通过调用assertTrue()方法来确认框架被正确设置。
例子中的后面两行是建立一个测试用例的实例,然后运行所有测试。如果所有设置都正确的话,你会得到下面的输出:
现在我们已经设置好了测试框架并正常运行。让我们开始我们的Biter类。
第一个测试
在我们测试Biter类的任何功能之前,我们必须确认这个类存在。我们当然也是写一个测试来做这件事:
require_once 'simpletest/unit_tester.php';
require_once 'simpletest/reporter.php';
class BiterTestCase extends UnitTestCase {
function testClassExists() {
$this->assertTrue(class_exists('Biter'), 'Biter class exists');
}
}
$test = new BiterTestCase('TDD Biter Test');
$test->run(new HtmlReporter());
?>
现在运行上面的测试。不要创建类或者包含其他内容;先运行测试。由于'Biter' 类不存在,你会看到下面的输出:
如你所见,我们的测试指出'Biter'类不存在。在TDD中,你总是从一个失败的测试开始工作。
现在我们必须让测试能够通过,做到这点需要创建Biter类。建立一个叫'biter.php'的新文件,写入如下代码:
Class Biter {
}
?>
然后把Biter类包含到我们的测试文件,加入如下行:
require_once 'biter.php';
现在再运行测试。这次它们会通过了,输出如下:
在创建Biter类的时候,我们走出了TDD的第一步。首先我们创建测试,然后再做实际的开发。
在目前我们的Biter类还没有任何功能,下面让我们开始我们的Biter类的第一部分:“开始吞噬”。
Biter返回正确的匹配吗?
我们希望第一步明确的是当我们调用bite()方法的时候Biter类将返回正确的匹配,因此,让我们就此写一个测试:
function testBiteReturnsAPatternMatch() {
$biter =& new Biter;
$haystack = 'foobar';
$this->assertEqual(
$biter->bite('/foo/', $haystack),
'foo',
'Biter returns correct match');
}
这个测试确认biter返回正确的匹配。它使用了assertEqual()方法,从名字我们就能知道这个方法的意思。把测试增加到我们的测试集,然后再运行测试。它会出错甚至返回一个致命错误,因为我们还没提供bite()方法。你会看到下面的输出: