PHP中的Pest插件开发:自定义Expectation与命令行输出的实践

PHP Pest 插件开发:自定义 Expectation 与命令行输出的实践

大家好!今天我们来深入探讨如何使用 PHP 的 Pest 测试框架开发自定义插件,重点关注自定义 Expectation 和命令行输出的实践。Pest 提供了强大的扩展机制,允许我们根据项目需求定制测试行为和报告方式,从而提高测试效率和代码质量。

1. Pest 插件基础

Pest 插件本质上是一个 PHP 类,它必须继承 PestPlugin 抽象类。这个抽象类定义了插件的基本接口,包括插件的注册和生命周期管理。

<?php

namespace MyCustomPlugin;

use PestPlugin;

class MyPlugin extends Plugin
{
    public function boot(): void
    {
        // 插件启动时执行的代码,例如注册自定义 Expectation
    }
}

boot() 方法是插件的核心入口,在这里我们可以注册自定义 Expectation、添加命令行选项、监听 Pest 事件等。

2. 自定义 Expectation

Expectation 是 Pest 中用于断言的核心机制。Pest 提供了丰富的内置 Expectation,但有时我们需要根据业务逻辑定义自己的 Expectation,以更精确地表达测试意图。

2.1 创建自定义 Expectation 类

自定义 Expectation 类需要实现 PestExpectation 接口。这个接口定义了一个 __call() 方法,用于处理动态调用的方法。

<?php

namespace MyCustomPluginExpectations;

use PestExpectation;
use PHPUnitFrameworkAssert;

class ContainsJson extends Expectation
{
    public function __call(string $name, array $arguments): Expectation
    {
        if ($name === 'toContainJson') {
            return $this->toContainJson(...$arguments);
        }

        return parent::__call($name, $arguments);
    }

    public function toContainJson(array $expectedJson): Expectation
    {
        $actualJson = json_decode($this->value, true);

        Assert::assertIsArray($actualJson, 'Value is not a valid JSON string.');
        Assert::assertIsArray($expectedJson, 'Expected value is not a valid JSON array.');

        foreach ($expectedJson as $key => $value) {
            Assert::assertArrayHasKey($key, $actualJson, "Key '{$key}' not found in JSON.");
            Assert::assertEquals($value, $actualJson[$key], "Value for key '{$key}' does not match.");
        }

        return $this;
    }
}

这个 ContainsJson Expectation 允许我们断言一个字符串是否包含指定的 JSON 数据。__call() 方法拦截了 toContainJson 方法的调用,并将控制权交给 toContainJson() 方法处理实际的断言逻辑。

2.2 注册自定义 Expectation

在插件的 boot() 方法中,我们需要注册自定义 Expectation,将其关联到 Pest 的 Expectation 体系中。

<?php

namespace MyCustomPlugin;

use PestPlugin;
use MyCustomPluginExpectationsContainsJson;

class MyPlugin extends Plugin
{
    public function boot(): void
    {
        $this->expectations([
            'string' => ContainsJson::class,
        ]);
    }
}

$this->expectations() 方法接收一个数组,键表示 Pest 的 Expectation 类型(例如 stringarrayint),值表示对应的自定义 Expectation 类。

现在,我们就可以在测试用例中使用自定义 Expectation 了:

<?php

it('contains the expected JSON data', function () {
    $jsonString = '{"name": "John Doe", "age": 30, "city": "New York"}';
    expect($jsonString)->toContainJson(['name' => 'John Doe', 'age' => 30]);
});

it('fails if the JSON string is invalid', function () {
    $invalidJsonString = '{"name": "John Doe", "age": 30, "city": "New York';
    expect($invalidJsonString)->toContainJson(['name' => 'John Doe', 'age' => 30]);
})->throws(PHPUnitFrameworkAssertionFailedError::class);

it('fails if the expected JSON is not found', function () {
    $jsonString = '{"name": "John Doe", "age": 30, "city": "New York"}';
    expect($jsonString)->toContainJson(['name' => 'Jane Doe', 'age' => 30]);
})->throws(PHPUnitFrameworkAssertionFailedError::class);

it('fails if the expected JSON value does not match', function () {
    $jsonString = '{"name": "John Doe", "age": 30, "city": "New York"}';
    expect($jsonString)->toContainJson(['name' => 'John Doe', 'age' => 31]);
})->throws(PHPUnitFrameworkAssertionFailedError::class);

3. 命令行输出定制

Pest 允许我们定制命令行输出,以便在测试过程中提供更详细的信息或自定义报告。

3.1 监听 Pest 事件

Pest 提供了丰富的事件系统,允许我们在测试的不同阶段执行自定义代码。我们可以监听这些事件,并在命令行输出自定义信息。

<?php

namespace MyCustomPlugin;

use PestEventsTestPassed;
use PestPlugin;
use SymfonyComponentConsoleOutputOutputInterface;

class MyPlugin extends Plugin
{
    public function boot(): void
    {
        TestPassed::subscribe(function (TestPassed $event, OutputInterface $output) {
            $testName = $event->test->description;
            $output->writeln("<info>Test Passed: {$testName}</info>");
        });
    }
}

TestPassed::subscribe() 方法注册一个事件监听器,当测试通过时,该监听器会被调用。监听器接收一个 TestPassed 事件对象和一个 OutputInterface 对象,我们可以使用 OutputInterface 对象将信息输出到命令行。

这个例子中,我们在测试通过时,将测试名称以绿色字体输出到命令行。

3.2 添加命令行选项

Pest 允许我们添加自定义命令行选项,以便在运行测试时控制插件的行为。

<?php

namespace MyCustomPlugin;

use PestConsoleArguments;
use PestPlugin;
use SymfonyComponentConsoleInputInputOption;

class MyPlugin extends Plugin
{
    public function boot(): void
    {
        Arguments::addOption('log-slow', null, InputOption::VALUE_NONE, 'Log slow tests');

        $this->afterEach(function () {
            if (Arguments::has('log-slow') && $this->time() > 0.5) {
                echo "Test {$this->description()} took {$this->time()} seconds.n";
            }
        });
    }
}

Arguments::addOption() 方法用于添加命令行选项。这个例子中,我们添加了一个名为 log-slow 的选项,用于记录执行时间超过 0.5 秒的测试。

$this->afterEach() 方法注册一个回调函数,该回调函数在每个测试用例执行后被调用。在回调函数中,我们检查 log-slow 选项是否被启用,以及测试执行时间是否超过 0.5 秒。如果条件满足,我们就将测试名称和执行时间输出到命令行。

现在,我们可以使用 --log-slow 选项来运行测试:

./vendor/bin/pest --log-slow

4. 完整示例:性能监控插件

让我们创建一个更完整的示例,一个性能监控插件,它可以记录每个测试的执行时间,并将执行时间超过阈值的测试报告出来。

<?php

namespace MyPerformancePlugin;

use PestEventsTestPassed;
use PestEventsTestFailed;
use PestPlugin;
use SymfonyComponentConsoleOutputOutputInterface;
use SymfonyComponentConsoleInputInputOption;
use PestConsoleArguments;

class PerformanceMonitor extends Plugin
{
    private float $threshold;

    public function boot(): void
    {
        // 添加命令行选项,用于设置性能阈值
        Arguments::addOption('performance-threshold', null, InputOption::VALUE_REQUIRED, 'Performance threshold in seconds', 1.0);

        // 获取命令行选项的值
        $this->threshold = (float) Arguments::get('performance-threshold');

        // 监听测试通过事件
        TestPassed::subscribe(function (TestPassed $event, OutputInterface $output) {
            $executionTime = $event->test->time();
            $testName = $event->test->description;

            if ($executionTime > $this->threshold) {
                $output->writeln("<bg=yellow;fg=black>SLOW TEST:</> <comment>{$testName}</comment> took <info>{$executionTime} seconds</info>");
            }
        });

        // 监听测试失败事件 (可选,也可以记录失败测试的性能)
        TestFailed::subscribe(function (TestFailed $event, OutputInterface $output) {
            $executionTime = $event->test->time();
            $testName = $event->test->description;

            if ($executionTime > $this->threshold) {
                $output->writeln("<bg=red;fg=white>SLOW FAILED TEST:</> <comment>{$testName}</comment> took <info>{$executionTime} seconds</info>");
            }
        });
    }
}

这个插件做了以下事情:

  • 添加了一个名为 performance-threshold 的命令行选项,用于设置性能阈值。默认值为 1.0 秒。
  • 监听 TestPassed 事件,当测试通过时,检查其执行时间是否超过阈值。如果超过阈值,就将测试名称和执行时间以黄色背景输出到命令行。
  • 监听 TestFailed 事件 (可选),当测试失败时,检查其执行时间是否超过阈值。如果超过阈值,就将测试名称和执行时间以红色背景输出到命令行。

要使用这个插件,首先确保你的 composer.json 文件中包含 my-performance-plugin/performance-monitor 依赖,并且在 pest.php 文件中注册了该插件。

然后,你可以运行以下命令来运行测试,并设置性能阈值为 0.5 秒:

./vendor/bin/pest --performance-threshold=0.5

5. 表格总结

功能 描述 实现方式
自定义 Expectation 扩展 Pest 的断言能力,根据业务逻辑定义自己的断言。 1. 创建实现 PestExpectation 接口的类。
2. 在 __call() 方法中拦截自定义方法调用。
3. 在自定义方法中执行断言逻辑,使用 PHPUnitFrameworkAssert 类。
4. 在插件的 boot() 方法中使用 $this->expectations() 方法注册自定义 Expectation。
命令行输出定制 定制 Pest 的命令行输出,以便在测试过程中提供更详细的信息或自定义报告。 1. 监听 Pest 事件,例如 TestPassedTestFailed
2. 在事件监听器中使用 SymfonyComponentConsoleOutputOutputInterface 对象将信息输出到命令行。
3. 使用 PestConsoleArguments 类添加自定义命令行选项。
4. 在事件监听器或测试用例中使用 Arguments::has()Arguments::get() 方法获取命令行选项的值。
性能监控插件 记录每个测试的执行时间,并将执行时间超过阈值的测试报告出来。 1. 添加 performance-threshold 命令行选项。
2. 监听 TestPassedTestFailed 事件。
3. 获取测试用例的执行时间。
4. 如果执行时间超过阈值,则将测试用例名称和执行时间输出到命令行。

6. 插件开发最佳实践

  • 命名空间: 使用独立的命名空间来组织插件代码,避免与其他代码冲突。
  • 单一职责: 插件应该只负责一个特定的功能,避免过度设计。
  • 可配置性: 允许用户通过命令行选项或配置文件来定制插件的行为。
  • 测试: 为插件编写单元测试,确保其功能正常。
  • 文档: 提供清晰的文档,说明插件的功能、用法和配置选项。

7. 深入理解 Pest 插件机制

Pest 的插件机制基于事件驱动架构。Pest 在测试的不同阶段触发各种事件,例如 BeforeAllBeforeEachTestPassedTestFailedAfterEachAfterAll。插件可以监听这些事件,并在事件发生时执行自定义代码。

Pest 使用 SymfonyComponentEventDispatcherEventDispatcher 组件来实现事件分发。我们可以使用 PestEvents 命名空间下的类来订阅事件。

8. Pest 插件的发布与共享

开发完成的 Pest 插件可以发布到 Packagist,以便其他开发者使用。

发布插件的步骤如下:

  1. composer.json 文件中定义插件的元数据,例如名称、描述、作者、许可证等。
  2. 将插件代码推送到 GitHub 或其他代码托管平台。
  3. 在 Packagist 上注册插件。

9. 总结:打造高效、可维护的测试流程

通过自定义 Expectation,我们能更精确地表达测试意图,提高测试代码的可读性。通过定制命令行输出,我们能更好地了解测试过程,及时发现问题。结合 Pest 的强大功能和灵活的扩展机制,我们可以打造高效、可维护的测试流程,保障代码质量。

希望今天的分享对大家有所帮助!谢谢!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注