第 15 章 扩展 PHPUnit

可以用多种方式对 PHPUnit 进行扩展,使编写测试更容易,以及对运行测试所得到的反馈进行定制。扩展 PHPUnit 时,一般从这些点入手:

从 PHPUnit_Framework_TestCase 派生子类

将自定义的断言和工具方法写在 PHPUnit_Framework_TestCase 的一个抽象子类中,然后从这个抽象子类派生你的测试用例类。这是扩展 PHPUnit 的最容易的方法。

编写自定义断言

编写自定义断言时,最佳实践是遵循 PHPUnit 自有断言的实现方式。正如 例 15.1中所示,assertTrue() 方法只是对 isTrue()assertThat() 方法的外包覆:isTrue() 创建了一个匹配器对象,将其传递给 assertThat() 进行评定。


例 15.1: PHPUnit_Framework_Assert 类的 assertTrue() 与 isTrue() 方法

  1. <?php
  2. abstract class PHPUnit_Framework_Assert
  3. {
  4. // ...
  5.  
  6. /**
  7. * 断言某个条件为真。
  8. *
  9. * @param boolean $condition
  10. * @param string $message
  11. * @throws PHPUnit_Framework_AssertionFailedError
  12. */
  13. public static function assertTrue($condition, $message = '')
  14. {
  15. self::assertThat($condition, self::isTrue(), $message);
  16. }
  17.  
  18. // ...
  19.  
  20. /**
  21. * 返回一个 PHPUnit_Framework_Constraint_IsTrue 匹配器对象
  22. *
  23. * @return PHPUnit_Framework_Constraint_IsTrue
  24. * @since Method available since Release 3.3.0
  25. */
  26. public static function isTrue()
  27. {
  28. return new PHPUnit_Framework_Constraint_IsTrue;
  29. }
  30.  
  31. // ...
  32. }?>

例 15.2展示了 PHPUnit_Framework_Constraint_IsTrue 是如何扩展针对匹配器对象(或约束)的抽象基类 PHPUnit_Framework_Constraint 的。


例 15.2: PHPUnit_Framework_Constraint_IsTrue 类

  1. <?php
  2. class PHPUnit_Framework_Constraint_IsTrue extends PHPUnit_Framework_Constraint
  3. {
  4. /**
  5. * 对参数 $other 进行约束评定。如果符合约束,
  6. * 返回 TRUE,否则返回 FALSE。
  7. *
  8. * @param mixed $other Value or object to evaluate.
  9. * @return bool
  10. */
  11. public function matches($other)
  12. {
  13. return $other === TRUE;
  14. }
  15.  
  16. /**
  17. * 返回代表此约束的字符串。
  18. *
  19. * @return string
  20. */
  21. public function toString()
  22. {
  23. return 'is true';
  24. }
  25. }?>

在实现 assertTrue()isTrue() 方法及 PHPUnit_Framework_Constraint_IsTrue 类时所付出的努力带来了一些好处,assertThat() 能够自动负责起断言的评定与任务簿记(例如为了统计目的而对其进行计数)工作。此外, isTrue() 方法还可以在配置仿件对象时用来作为匹配器。

实现 PHPUnit_Framework_TestListener

例 15.3展示了 PHPUnit_Framework_TestListener 接口的一个简单实现。


例 15.3: 简单的测试监听器

  1. <?php
  2. class SimpleTestListener implements PHPUnit_Framework_TestListener
  3. {
  4. public function addError(PHPUnit_Framework_Test $test, Exception $e, $time)
  5. {
  6. printf("Error while running test '%s'.\n", $test->getName());
  7. }
  8.  
  9. public function addFailure(PHPUnit_Framework_Test $test, PHPUnit_Framework_AssertionFailedError $e, $time)
  10. {
  11. printf("Test '%s' failed.\n", $test->getName());
  12. }
  13.  
  14. public function addIncompleteTest(PHPUnit_Framework_Test $test, Exception $e, $time)
  15. {
  16. printf("Test '%s' is incomplete.\n", $test->getName());
  17. }
  18.  
  19. public function addRiskyTest(PHPUnit_Framework_Test $test, Exception $e, $time)
  20. {
  21. printf("Test '%s' is deemed risky.\n", $test->getName());
  22. }
  23.  
  24. public function addSkippedTest(PHPUnit_Framework_Test $test, Exception $e, $time)
  25. {
  26. printf("Test '%s' has been skipped.\n", $test->getName());
  27. }
  28.  
  29. public function startTest(PHPUnit_Framework_Test $test)
  30. {
  31. printf("Test '%s' started.\n", $test->getName());
  32. }
  33.  
  34. public function endTest(PHPUnit_Framework_Test $test, $time)
  35. {
  36. printf("Test '%s' ended.\n", $test->getName());
  37. }
  38.  
  39. public function startTestSuite(PHPUnit_Framework_TestSuite $suite)
  40. {
  41. printf("TestSuite '%s' started.\n", $suite->getName());
  42. }
  43.  
  44. public function endTestSuite(PHPUnit_Framework_TestSuite $suite)
  45. {
  46. printf("TestSuite '%s' ended.\n", $suite->getName());
  47. }
  48. }
  49. ?>

例 15.4展示了如何从抽象类 PHPUnit_Framework_BaseTestListener 派生子类,这个抽象类为所有接口方法提供了空白实现,这样你就只需要指定那些在你的使用情境下有意义的接口方法。


例 15.4: 使用测试监听器基类

  1. <?php
  2. class ShortTestListener extends PHPUnit_Framework_BaseTestListener
  3. {
  4. public function endTest(PHPUnit_Framework_Test $test, $time)
  5. {
  6. printf("Test '%s' ended.\n", $test->getName());
  7. }
  8. }
  9. ?>

“测试监听器”一节中可以看到如何配置 PHPUnit 来将测试监听器附加到测试执行过程上。

从 PHPUnit_Extensions_TestDecorator 派生子类

可以将测试用例或者测试套件包装在 PHPUnit_Extensions_TestDecorator 的子类中并运用 Decorator(修饰器)设计模式来在测试运行前后执行一些动作。

PHPUnit 了包含了一个具体的测试修饰器:PHPUnit_Extensions_RepeatedTest。它用于重复运行某个测试,并且只在全部循环中都成功时计为成功。

例 15.5展示了测试修饰器 PHPUnit_Extensions_RepeatedTest 的一个删减版本,用以说明如何编写你自己的测试修饰器。


例 15.5: RepeatedTest 修饰器

  1. <?php
  2. require_once 'PHPUnit/Extensions/TestDecorator.php';
  3.  
  4. class PHPUnit_Extensions_RepeatedTest extends PHPUnit_Extensions_TestDecorator
  5. {
  6. private $timesRepeat = 1;
  7.  
  8. public function __construct(PHPUnit_Framework_Test $test, $timesRepeat = 1)
  9. {
  10. parent::__construct($test);
  11.  
  12. if (is_integer($timesRepeat) &&
  13. $timesRepeat >= 0) {
  14. $this->timesRepeat = $timesRepeat;
  15. }
  16. }
  17.  
  18. public function count()
  19. {
  20. return $this->timesRepeat * $this->test->count();
  21. }
  22.  
  23. public function run(PHPUnit_Framework_TestResult $result = NULL)
  24. {
  25. if ($result === NULL) {
  26. $result = $this->createResult();
  27. }
  28.  
  29. for ($i = 0; $i < $this->timesRepeat && !$result->shouldStop(); $i++) {
  30. $this->test->run($result);
  31. }
  32.  
  33. return $result;
  34. }
  35. }
  36. ?>

实现 PHPUnit_Framework_Test

PHPUnitFramework_Test 接口是比较狭义的,十分容易实现。举例来说,你可以自行为 PHPUnit_Framework_Test 编写一个类似于 PHPUnit_Framework_TestCase 的实现来运行数据驱动测试_。

例 15.6展示了一个数据驱动的测试用例类,对来自 CSV 文件内的值进行比较。这个文件内的每个行看起来类似于 foo;bar,第一个值是期望值,第二个值则是实际值。


例 15.6: 一个数据驱动的测试

  1. <?php
  2. class DataDrivenTest implements PHPUnit_Framework_Test
  3. {
  4. private $lines;
  5.  
  6. public function __construct($dataFile)
  7. {
  8. $this->lines = file($dataFile);
  9. }
  10.  
  11. public function count()
  12. {
  13. return 1;
  14. }
  15.  
  16. public function run(PHPUnit_Framework_TestResult $result = NULL)
  17. {
  18. if ($result === NULL) {
  19. $result = new PHPUnit_Framework_TestResult;
  20. }
  21.  
  22. foreach ($this->lines as $line) {
  23. $result->startTest($this);
  24. PHP_Timer::start();
  25. $stopTime = NULL;
  26.  
  27. list($expected, $actual) = explode(';', $line);
  28.  
  29. try {
  30. PHPUnit_Framework_Assert::assertEquals(
  31. trim($expected), trim($actual)
  32. );
  33. }
  34.  
  35. catch (PHPUnit_Framework_AssertionFailedError $e) {
  36. $stopTime = PHP_Timer::stop();
  37. $result->addFailure($this, $e, $stopTime);
  38. }
  39.  
  40. catch (Exception $e) {
  41. $stopTime = PHP_Timer::stop();
  42. $result->addError($this, $e, $stopTime);
  43. }
  44.  
  45. if ($stopTime === NULL) {
  46. $stopTime = PHP_Timer::stop();
  47. }
  48.  
  49. $result->endTest($this, $stopTime);
  50. }
  51.  
  52. return $result;
  53. }
  54. }
  55.  
  56. $test = new DataDrivenTest('data_file.csv');
  57. $result = PHPUnit_TextUI_TestRunner::run($test);
  58. ?>
  1. PHPUnit 4.8.0 by Sebastian Bergmann and contributors.
  2.  
  3. .F
  4.  
  5. Time: 0 seconds
  6.  
  7. There was 1 failure:
  8.  
  9. 1) DataDrivenTest
  10. Failed asserting that two strings are equal.
  11. expected string <bar>
  12. difference < x>
  13. got string <baz>
  14. /home/sb/DataDrivenTest.php:32
  15. /home/sb/DataDrivenTest.php:53
  16.  
  17. FAILURES!
  18. Tests: 2, Failures: 1.

原文: http://www.phpunit.cn/manual/4.8/zh_cn/extending-phpunit.html