PHPUnit袖珍指南 (6-8章)
时间:2007-02-05 来源:lib
第六章 装置器
编写测试最耗时的部分是边编写设置整个程序到达一个已知状态,而后在测试结束后返回到原始状态。这个已知状态叫做测试的装置器。
在例5中,装置器很简单,只是存储在变量$fixture中的数组。多数情况下,装置器会比简单数组复杂,设置代码也会相应增长。当你写几个类似的装置器时这个问题变得更糟糕。没有测试框架的帮助,我们不得不重复很多代码,为每个测试设置装置器。
PHPUnit支持共享设置代码。在测试方法运行之前,模板类方法setUp( )会被调用。setUp( )用于创建要测试的对象,当测试方法结束运行时,无论成功失败,另一个模板方法tearDown( )会被调用。tearDown( )是用于清除测试用于的对象的。
我们现在重构例5,使用setUp( )解决以前有的代码重复的问题。首先,我们定义变量$fixture的实例,用于替代局部方法中的变量。然后,我们将创建数组fixture的装置器放入方法setUp( )中。最后,我们将从测试方法中移除多余代码,使用新引进的变量实例,$this->fixture,使用assertEquals( )断言方法替代本地方法变量$fixture。
require_once 'PHPUnit2/Framework/TestCase.php';
class ArrayTest extends PHPUnit2_Framework_TestCase {
protected $fixture;
protected function setUp( ) {
// Create the Array fixture.
$this->fixture = Array( );
}
public function testNewArrayIsEmpty( ) {
// Assert that the size of the Array fixture is 0.
$this->assertEquals(0, sizeof($this->fixture));
}
public function testArrayContainsAnElement( ) {
// Add an element to the Array fixture.
$this->fixture[] = 'Element';
// Assert that the size of the Array fixture is 1.
$this->assertEquals(1, sizeof($this->fixture));
}
}
?>
每个测试运行时,各调用setUp( )和tearDown( )一次。尽管在一个测试用例类中所有的测试方法只运行一次设置和解除代码,这看起来很简单,但这么做使得书写互相完全独立的测试相当困难。
除了setUp( )和tearDown( )在每个测试中只运行一次外,每个测试在测试用例类的新实例中也只运行一次(参见本书后的PHPUnit的实现部分)。
6-1. 多用setUp( ),少用tearDown( )
理论上,setUp( )和tearDown( )是很好的对称关系,但实践中不是这样。如果你已经在setUp( )中分配了外部资源,如文件或socket,你只需要实现tearDown( )。 如果setUp() 只创造简单的PHP 对象,一般就可以忽略tearDown() 。 但是, 如果在setUp()创建了很多对象,你也许想要在tearDown()中用unset() 函数复位那些对象,这些对象会被垃圾收集器收集。测试用例对象的垃圾收集是不可预测的。
6-2. 变量
当有二个细微不同的设置过程的测试会怎么样? 有二种可能性:
如果setUp() 代码只有少许不想同,将不同的代码移出setUp(),放入测试方法。
如果setUp()完全不同,就要一个不同的测试用例类。命名不同的类名,体现在设置方法中。
6-3. 套件级设定
PHPUnit没有为套件的设定提供方便的支持。没有什么真正的原因要求在测试之间分享装置器,这种情况一般源于未解决的设计问题。
在测试间共享装置器的一个好例子是数据库连接: 登录数据库一次,然后重复利用数据库连接,而不是为每个测试创建新连接。这使测试运行得更快。要实现这种情况,将数据库测试写在名为DatabaseTests得测试用例类中,并且包装测试套件在TestSetup 装饰器对象中,重载setUp(),实现打开数据库连接,在tearDown()中关闭连接,如例6。可以通过DatabaseTestSetup 装饰器调用,通过DatabaseTests运行测试,例如,PHPUnit 的命令行测试器通过phpunit DatabaseTestSetup运行。
例6.书写套件层次的设置装饰器
require_once 'PHPUnit2/Framework/TestSuite.php';
require_once 'PHPUnit2/Extensions/TestSetup.php';
class DatabaseTestSetup extends PHPUnit2_Extensions_TestSetup
{
protected $connection = NULL;
protected function setUp( ) {
$this->connection = new PDO(
'mysql:host=wopr;dbname=test',
'root',
''
);
}
protected function tearDown( ) {
$this->connection = NULL;
}
public static function suite( ) {
return new DatabaseTestSetup(
new PHPUnit2_Framework_TestSuite('DatabaseTests')
);
}
}
?>
怎么强调都不为过,在测试间共享装置器会减少测试的价值。隐藏的设计问题是,对象捆绑的太紧密。使用残根来书写测试能够解决隐藏的设计问题,得到更好的结果(参见本书后的残根部分),这比忽视改进设计机会,创建运行时有依赖关系的测试要好的多。
第七章 测试异常和性能回归
PHPUnit提供了二个扩展,基于测试类的标准基类PHPUnit2_Framework_TestCase,协助为书写异常和性能回归测试。
7-1 异常
怎么测试异常?当异常抛出时,无法直接使用断言。相反,必须使用PHP 的异常处理机制来书写测试。以下例子示范了入阁测试异常:
require_once 'PHPUnit2/Framework/TestCase.php';
class ExceptionTest extends PHPUnit2_Framework_TestCase {
public function testException( ) {
try {
// … Code that is expected to raise an
// Exception …
$this->fail('No Exception has been raised.');
}
catch (Exception $expected) {
}
}
}
?>
如果预计要抛出异常的代码没有抛出异常,随后调用的fail()函数(参见本书后的表7)将暂停测试,抛出一个测试有问题的信号。如果预计的异常抛出,每个catch的语句块都会被执行,测试将继续执行。
另外一个测试异常的方法是,测试类可以继承PHPUnit2 _ Extensions_ExceptionTestCase,这可以测试被测试的代码是否抛出了异常。例7展示了如何子类化PHPUnit2_Extensions_ExceptionTestCase 和使用它的setExpectedException() 方法设置预计的异常。如果预计的异常没有抛出,测试将算作是一次失败。
例7.使用PHPUnit2_Extensions_ExceptionTestCase
require_once 'PHPUnit2/Extensions/ExceptionTestCase.php';
class ExceptionTest extends PHPUnit2_Extensions_
ExceptionTestCase {
public function testException( ) {
$this->setExpectedException('Exception');
}
}
?>
phpunit ExceptionTest
PHPUnit 2.3.0 by Sebastian Bergmann.
F
Time: 0.006798
There was 1 failure:
1) testException(ExceptionTest)
Expected exception Exception
FAILURES!!!
Tests run: 1, Failures: 1, Errors: 0, Incomplete Tests: 0.
表1显示的PHPUnit2_Extensions_ExceptionTestCase 实现的外部协议。
表1 扩展TestCase的外部协议方法
方法
描述
void setExpectedException(String $exceptionName)
将$exceptionName变量设置为预计的异常名
String getExpectedException( )
返回期望的异常名称
7-2 性能回归
从PHPUnit2_Extensions _ PerformanceTestCase扩展测试类,可以用于测试函数或方法的调用,如,是否超出的运行时限。
例8展示了怎么继承PHPUnit2_Extensions _ PerformanceTestCase类,使用setMaxRunningTime() 方法设置测试的最大运行时间。如果测试的执行超出了时限,这可以作为测试失败了。
例8 使用PHPUnit2_Extensions_PerformanceTestCase
require_once 'PHPUnit2/Extensions/PerformanceTestCase.php';
class PerformanceTest extends PHPUnit2_Extensions_
PerformanceTestCase {
public function testPerformance( ) {
$this->setMaxRunningTime(2);
sleep(1);
}
}
?>
Table 2. Performance TestCase external protocols Method
表2 性能测试用例的外部协议方法
表2
方法
描述
void setMaxRunningTime(integer $maxRunningTime)
将变量$maxRunningTime(秒)设为测试运行的最大时间。
integer getMaxRunningTime( )
返回测试允许的最大运行时间
第八章 未完成测试
当开始书写新的测试用例类时,你也许想要从空的测试方法开始,譬如:
public function testSomething( ) {
}
我们必须跟踪书写的每个测试。空测试方法的问题是,它们被PHPUnit 框架解释作执行成功。这样产生的测试报告是没有用的。您不能区分测试实际上是成功了或是未完成。在未完成的测试方法中调用$this->fail()也不行,因为这被解释作为失败。这和未完成测试被错误解释成功一样糟糕。
如果我们认为一个成功的测试可以亮绿灯,测试失败亮红灯,那我们就需要黄灯来标记测试未完成。PHPUnit2 _ Framework_IncompleteTest是这个标志的接口,标记测试方法抛出的测试未完成或当前未完成的异常。PHPUnit2_Framework_IncompleteTestError 是这个接口的标准实现。
例9显示了测试用例类SampleTest包含了一个测试方法testSomething(),通过抛出PHPUnit2 _ Framework_IncompleteTestError异常,我们将此测试标为未完成测试。
例9.将测试标记为未完成
require_once 'PHPUnit2/Framework/TestCase.php';
require_once 'PHPUnit2/Framework/IncompleteTestError.php';
class SampleTest extends PHPUnit2_Framework_TestCase {
public function testSomething( ) {
// Optional: Test anything here, if you want.
$this->assertTrue(TRUE, 'This should already work.');
// Stop here and mark this test as incomplete.
// You could use any Exception which implements the
// PHPUnit2_Framework_IncompleteTest interface.
throw new PHPUnit2_Framework_IncompleteTestError(
'This test has not been implemented yet.'
);
}
}
?>
An incomplete test is denoted by an I in the output of the PHPUnit command-line test runner, as shown in the following example:
一个未完成的测试在PHPUnit 命令行测试器中由I 表示,如下例所示:
phpunit SampleTest
PHPUnit 2.3.0 by Sebastian Bergmann.
I
Time: 0.006657
There was 1 incomplete test case:
1) testSomething(SampleTest)
This test has not been implemented yet.
OK, but incomplete test cases!!!
Tests run: 1, incomplete test cases: 1.
相关阅读 更多 +
排行榜 更多 +