Extending PHPUnit

扩展PHPUnit

PHPUnit可以通过各种方式进行扩展,使测试的编写更加简单,并可以定制从运行测试中获得的反馈。以下是扩展PHPUnit的常见起点。

子类PHPUnit \ Framework \ TestCase

在抽象的子类中编写自定义断言和实用程序方法,PHPUnit\Framework\TestCase并从该类派生您的测试用例类。这是扩展PHPUnit最简单的方法之一。

编写自定义断言

在编写自定义断言时,遵循PHPUnit自己的断言如何实现是最佳实践。正如你在例14.1中看到的,这个assertTrue()方法只是一个包围isTrue()assertThat()方法的包装器:isTrue()创建一个传递给assertThat()评估的匹配器对象。

例14.1:PHPUnit_Framework_Assert类的assertTrue()和isTrue()方法

<?php use PHPUnit\Framework\TestCase; abstract class PHPUnit_Framework_Assert { // ... /** * Asserts that a condition is true. * * @param boolean $condition * @param string $message * @throws PHPUnit_Framework_AssertionFailedError */ public static function assertTrue($condition, $message = '') { self::assertThat($condition, self::isTrue(), $message } // ... /** * Returns a PHPUnit_Framework_Constraint_IsTrue matcher object. * * @return PHPUnit_Framework_Constraint_IsTrue * @since Method available since Release 3.3.0 */ public static function isTrue() { return new PHPUnit_Framework_Constraint_IsTrue; } // ... }?>

例14.2显示了如何PHPUnit_Framework_Constraint_IsTrue扩展匹配器对象(或约束)的抽象基类,PHPUnit_Framework_Constraint

例14.2:PHPUnit_Framework_Constraint_IsTrue类

<?php use PHPUnit\Framework\TestCase; class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint { /** * Evaluates the constraint for parameter $other. Returns true if the * constraint is met, false otherwise. * * @param mixed $other Value or object to evaluate. * @return bool */ public function matches($other) { return $other === true; } /** * Returns a string representation of the constraint. * * @return string */ public function toString() { return 'is true'; } }?>

实施assertTrue()isTrue()方法以及PHPUnit_Framework_Constraint_IsTrue类的努力产生了assertThat()自动处理评估断言和簿记任务的好处,例如统计数据。此外,该isTrue()方法可以在配置模拟对象时用作匹配器。

实现PHPUnit_Framework_TestListener

例14.3给出了一个PHPUnit_Framework_TestListener接口的简单实现。

例14.3:一个简单的测试监听器

<?php use PHPUnit\Framework\TestCase; class SimpleTestListener implements PHPUnit_Framework_TestListener { public function addError(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("Error while running test '%s'.\n", $test->getName() } public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time) { printf("Test '%s' failed.\n", $test->getName() } public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("Test '%s' is incomplete.\n", $test->getName() } public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("Test '%s' is deemed risky.\n", $test->getName() } public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time) { printf("Test '%s' has been skipped.\n", $test->getName() } public function startTest(PHPUnit_Framework_Test $test) { printf("Test '%s' started.\n", $test->getName() } public function endTest(PHPUnit_Framework_Test $test, $time) { printf("Test '%s' ended.\n", $test->getName() } public function startTestSuite(PHPUnit_Framework_TestSuite $suite) { printf("TestSuite '%s' started.\n", $suite->getName() } public function endTestSuite(PHPUnit_Framework_TestSuite $suite) { printf("TestSuite '%s' ended.\n", $suite->getName() } } ?>

例14.4展示了如何对PHPUnit_Framework_BaseTestListener抽象类进行子类化,从而使您只指定对您的用例感兴趣的接口方法,同时为所有其他类提供空实现。

例14.4:使用基本测试监听器

<?php use PHPUnit\Framework\TestCase; class ShortTestListener extends PHPUnit_Framework_BaseTestListener { public function endTest(PHPUnit_Framework_Test $test, $time) { printf("Test '%s' ended.\n", $test->getName() } } ?>

在“测试监听器”一节中,您可以看到如何配置PHPUnit以将测试监听器附加到测试执行。

子类PHPUnit_Extensions_TestDecorator

您可以将测试用例或测试套件包装在一个子类中,PHPUnit_Extensions_TestDecorator并使用Decorator设计模式在测试运行之前和之后执行一些操作。

PHPUnit附带一个具体的测试装饰器:PHPUnit_Extensions_RepeatedTest。它用于重复运行测试,并且只有在所有迭代都成功时才算作成功。

例14.5展示了一个PHPUnit_Extensions_RepeatedTest测试装饰器的简化版本,它说明了如何编写你自己的测试装饰器。

例14.5:RepeatedTest装饰器

<?php use PHPUnit\Framework\TestCase; require_once 'PHPUnit/Extensions/TestDecorator.php'; class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator { private $timesRepeat = 1; public function __construct(PHPUnit_Framework_Test $test, $timesRepeat = 1) { parent::__construct($test if (is_integer($timesRepeat) && $timesRepeat >= 0) { $this->timesRepeat = $timesRepeat; } } public function count() { return $this->timesRepeat * $this->test->count( } public function run(PHPUnit_Framework_TestResult $result = null) { if ($result === null) { $result = $this->createResult( } for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop( $i++) { $this->test->run($result } return $result; } } ?>

实施PHPUnit_Framework_Test

PHPUnit_Framework_Test接口是有限且易于实现。例如,您可以编写PHPUnit_Framework_Test比此更简单的实现,PHPUnit\Framework\TestCase并运行数据驱动的测试

示例14.6显示了一个数据驱动的测试用例类,它将来自文件的值与逗号分隔值(CSV)进行比较。这样的文件的每一行看起来像foo;bar,第一个值是我们期望的值,第二个值是实际值。

例14.6:数据驱动的测试

<?php use PHPUnit\Framework\TestCase; class DataDrivenTest implements PHPUnit_Framework_Test { private $lines; public function __construct($dataFile) { $this->lines = file($dataFile } public function count() { return 1; } public function run(PHPUnit_Framework_TestResult $result = null) { if ($result === null) { $result = new PHPUnit_Framework_TestResult; } foreach ($this->lines as $line) { $result->startTest($this PHP_Timer::start( $stopTime = null; list($expected, $actual) = explode(';', $line try { PHPUnit_Framework_Assert::assertEquals( trim($expected), trim($actual) } catch (PHPUnit_Framework_AssertionFailedError $e) { $stopTime = PHP_Timer::stop( $result->addFailure($this, $e, $stopTime } catch (Exception $e) { $stopTime = PHP_Timer::stop( $result->addError($this, $e, $stopTime } if ($stopTime === null) { $stopTime = PHP_Timer::stop( } $result->endTest($this, $stopTime } return $result; } } $test = new DataDrivenTest('data_file.csv' $result = PHPUnit_TextUI_TestRunner::run($test ?>

PHPUnit 6.4.0 by Sebastian Bergmann and contributors. .F Time: 0 seconds There was 1 failure: 1) DataDrivenTest Failed asserting that two strings are equal. expected string <bar> difference < x> got string <baz> /home/sb/DataDrivenTest.php:32 /home/sb/DataDrivenTest.php:53 FAILURES! Tests: 2, Failures: 1.