第 14 章 扩展 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() 方法

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

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


例 14.2: PHPUnit_Framework_Constraint_IsTrue 类

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

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

实现 PHPUnit\Framework\TestListener

例 14.3展示了 PHPUnit\Framework\TestListener 接口的一个简单实现。


例 14.3: 简单的测试监听器

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

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


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

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

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

从 PHPUnit_Extensions_TestDecorator 派生子类

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

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

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


例 14.5: RepeatedTest 修饰器

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

实现 PHPUnit_Framework_Test

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

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


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

  1. <?php
  2. use PHPUnit\Framework\TestCase;
  3.  
  4. class DataDrivenTest implements PHPUnit_Framework_Test
  5. {
  6. private $lines;
  7.  
  8. public function __construct($dataFile)
  9. {
  10. $this->lines = file($dataFile);
  11. }
  12.  
  13. public function count()
  14. {
  15. return 1;
  16. }
  17.  
  18. public function run(PHPUnit_Framework_TestResult $result = null)
  19. {
  20. if ($result === null) {
  21. $result = new PHPUnit_Framework_TestResult;
  22. }
  23.  
  24. foreach ($this->lines as $line) {
  25. $result->startTest($this);
  26. PHP_Timer::start();
  27. $stopTime = null;
  28.  
  29. list($expected, $actual) = explode(';', $line);
  30.  
  31. try {
  32. PHPUnit_Framework_Assert::assertEquals(
  33. trim($expected), trim($actual)
  34. );
  35. }
  36.  
  37. catch (PHPUnit_Framework_AssertionFailedError $e) {
  38. $stopTime = PHP_Timer::stop();
  39. $result->addFailure($this, $e, $stopTime);
  40. }
  41.  
  42. catch (Exception $e) {
  43. $stopTime = PHP_Timer::stop();
  44. $result->addError($this, $e, $stopTime);
  45. }
  46.  
  47. if ($stopTime === null) {
  48. $stopTime = PHP_Timer::stop();
  49. }
  50.  
  51. $result->endTest($this, $stopTime);
  52. }
  53.  
  54. return $result;
  55. }
  56. }
  57.  
  58. $test = new DataDrivenTest('data_file.csv');
  59. $result = PHPUnit_TextUI_TestRunner::run($test);
  60. ?>
  1. PHPUnit 6.5.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.

原文: https://phpunit.de/manual/6.5/zh_cn/extending-phpunit.html