10. 扩展 PHPUnit

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

PHPUnit\Framework\TestCase 的子类

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

编写自定义断言

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

示例 10.1 PHPUnit\Framework\Assert 类的 assertTrue() 和 isTrue() 方法

  1. <?php declare(strict_types=1);
  2. namespace PHPUnit\Framework;
  3. use PHPUnit\Framework\Constraint\IsTrue;
  4. abstract class Assert
  5. {
  6. // ...
  7. public static function assertTrue($condition, string $message = ''): void
  8. {
  9. static::assertThat($condition, static::isTrue(), $message);
  10. }
  11. // ...
  12. public static function isTrue(): IsTrue
  13. {
  14. return new IsTrue;
  15. }
  16. // ...
  17. }

示例 10.2 展示了 PHPUnit\Framework\Constraint\IsTrue 是如何扩展针对匹配器对象(或约束)的抽象基类 PHPUnit\Framework\Constraint 的。

示例 10.2 PHPUnit\FrameworkConstraint\IsTrue 类

  1. <?php declare(strict_types=1);
  2. namespace PHPUnit\Framework\Constraint;
  3. use PHPUnit\Framework\Constraint;
  4. final class IsTrue extends Constraint
  5. {
  6. public function toString(): string
  7. {
  8. return 'is true';
  9. }
  10. protected function matches($other): bool
  11. {
  12. return $other === true;
  13. }
  14. }

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

扩展测试执行器

PHPUnit 的测试执行器可以通过注册实现了一个或多个以下接口的对象来进行扩展:

  • AfterIncompleteTestHook
  • AfterLastTestHook
  • AfterRiskyTestHook
  • AfterSkippedTestHook
  • AfterSuccessfulTestHook
  • AfterTestErrorHook
  • AfterTestFailureHook
  • AfterTestWarningHook
  • AfterTestHook
  • BeforeFirstTestHook
  • BeforeTestHook

每个“hook”(表示上面列出的各个接口)都代表一个可能在测试执行过程中发生的事件。

有关如何在 PHPUnit 的 XML 配置中注册扩展的详细信息,参见 <extensions> 元素

示例 10.3 展示了一个实现了 BeforeFirstTestHookAfterLastTestHook 的扩展:

示例 10.3 测试执行器扩展示例

  1. <?php declare(strict_types=1);
  2. namespace Vendor;
  3. use PHPUnit\Runner\BeforeFirstTestHook;
  4. use PHPUnit\Runner\AfterLastTestHook;
  5. final class MyExtension implements BeforeFirstTestHook, AfterLastTestHook
  6. {
  7. public function executeBeforeFirstTest(): void
  8. {
  9. // 运行第一个测试之前调用
  10. }
  11. public function executeAfterLastTest(): void
  12. {
  13. // 运行完最后一个测试之后调用
  14. }
  15. }

配置扩展

假定扩展接受配置值,则你可以对 PHPUnit 扩展进行配置。

示例 10.4 展示了如何通过为扩展类添加 __constructor() 定义让扩展可配置:

示例 10.4 带有构造函数的测试执行器扩展

  1. <?php declare(strict_types=1);
  2. namespace Vendor;
  3. use PHPUnit\Runner\BeforeFirstTestHook;
  4. use PHPUnit\Runner\AfterLastTestHook;
  5. final class MyConfigurableExtension implements BeforeFirstTestHook, AfterLastTestHook
  6. {
  7. protected $config_value_1 = '';
  8. protected $config_value_2 = 0;
  9. public function __construct(string $value1 = '', int $value2 = 0)
  10. {
  11. $this->config_value_1 = $config_1;
  12. $this->config_value_2 = $config_2;
  13. }
  14. public function executeBeforeFirstTest(): void
  15. {
  16. if (strlen($this->config_value_1) {
  17. echo 'Testing with configuration value: ' . $this->config_value_1;
  18. }
  19. }
  20. public function executeAfterLastTest(): void
  21. {
  22. if ($this->config_value_2 > 10) {
  23. echo 'Second config value is OK!';
  24. }
  25. }
  26. }

要通过 XML 给扩展输入配置,必须更新 XML 配置文件的 extensions 段来让其拥有配置值,如示例 10.5 中所示:

示例 10.5 测试执行器扩展配置

  1. <extensions>
  2. <extension class="Vendor\MyUnconfigurableExtension" />
  3. <extension class="Vendor\MyConfigurableExtension">
  4. <arguments>
  5. <string>Hello world!</string>
  6. <int>15</int>
  7. </arguments>
  8. </extension>
  9. </extensions>

有关如何使用 arguments 配置的详细信息,参见 <arguments> 元素

请记住:所有配置都是可选的,因此要确保你的配置要么要有健全的默认值,要么它就应当在缺失配置的时候禁用自身。