yoast/wp-test-utils

WordPress插件和主题测试的PHPUnit跨版本兼容层

1.2.0 2023-09-27 10:25 UTC

README

Version CS Build Status Test Build Status Coverage Status

Minimum PHP Version License: BSD3

此库包含了一套用于运行WordPress插件和主题自动化测试的工具。

要求

  • PHP 5.6或更高版本。

以下包将通过Composer自动要求

安装

要安装此包,请运行

composer require --dev yoast/wp-test-utils

要更新此包,请运行

composer update --dev yoast/wp-test-utils --with-dependencies

功能

此库包含了一套用于运行WordPress插件和主题自动化测试的工具。

使用BrainMonkey运行测试的实用程序

与BrainMonkey一起使用的基本TestCase

TestCase的功能

  1. 通过PHPUnit Polyfills包与PHPUnit 5.7 - 9.x的跨版本兼容性。
  2. BrainMonkey和Mockery的设置和拆卸已自动处理。
  3. 使用Mockery期望的测试不会被标记为“有风险”,即使没有断言。
  4. 提供了BrainMonkey本地stubTranslationFunctions()stubEscapeFunctions()函数的替代实现。
    BrainMonkey本地函数创建的存根将在存根函数是一个转义函数(如esc_html__())时应用基本的HTML转义。
    这些函数的替代实现将创建存根,该存根将返回原始值而不进行更改。这使得创建测试更容易,因为$expected值不需要考虑HTML转义。
    注意:应选择性使用替代实现。
  5. 用于设置生成输出的期望的辅助函数。
  6. 用于创建不可用类的“虚拟”测试替身的辅助函数。

实现示例

<?php
namespace PackageName\Tests;

use Yoast\WPTestUtils\BrainMonkey\TestCase;

class FooTest extends TestCase {
    protected function set_up() {
        parent::set_up();
        // Your own additional setup.
    }

    protected function tear_down() {
        // Your own additional tear down.
        parent::tear_down();
    }

    public function testAFunctionContainingStringTranslating() {
        $this->stubTranslationFunctions(); // No HTML escaping will be applied.
        // Or:
        \Brain\Monkey\Functions\stubTranslationFunctions(); // HTML escaping will be applied.

        // Test your code.
    }

    public function testAFunctionContainingOutputEscaping() {
        $this->stubEscapeFunctions(); // No HTML escaping will be applied.
        // Or:
        \Brain\Monkey\Functions\stubEscapeFunctions(); // HTML escaping will be applied.

        // Test your code.
    }
}

与BrainMonkey一起使用的YoastTestCase

此TestCase的功能

  1. 如上所述,所有基本TestCase的好处。

  2. 默认情况下,以下WordPress函数将被存根,除BrainMonkey已提供的存根

实现示例

<?php
namespace PackageName\Tests;

use Yoast\WPTestUtils\BrainMonkey\YoastTestCase;

class FooTest extends YoastTestCase {
    // Your test code.
}

与BrainMonkey一起使用的引导文件

通常,使用Composer vendor/autoload.php文件作为PHPUnit的引导文件就足够了。

然而,在测试WordPress插件和主题的上下文中,可以访问某些WordPress原生声明的常量是有用的。

与BrainMonkey一起使用的引导文件将确保以下常量被定义

此外,在运行测试之前,它还会清除PHP Opcache。

实现示例

<?php
// File: tests/bootstrap.php
require_once dirname( __DIR__ ) . '/vendor/yoast/wp-test-utils/src/BrainMonkey/bootstrap.php';
require_once dirname( __DIR__ ) . '/vendor/autoload.php';

要告诉PHPUnit使用此引导文件,请在命令行中使用--bootstrap tests/bootstrap.php或在你的phpunit.xml.dist文件中添加bootstrap="tests/bootstrap.php"属性。

创建不可用类的测试替身辅助函数

💡此功能解决的问题已在Mockery 1.6.0中修复,因此如果您使用Mockery 1.6.0或更高版本针对PHP 8.2或更高版本进行测试运行,您可能不再需要此解决方案。

为什么您可能需要为不可用的类创建测试替身

通常,通过使用Mockery::mock()Mockery::mock( 'Unavailable' )来创建不可用类的模拟。

当测试或被测试代码在这样一个模拟上设置属性时,这将在PHP >= 8.2上导致一个"创建动态属性已弃用"警告,这可能导致测试出错。

如果您确信设置的模拟属性是已声明的属性或通过魔术方法在所模拟的类上支持的属性,则通常情况下,被测试代码不会导致此弃用警告,但您的测试现在却出现了。

这主要是Mockery的问题。有关如何在Mockery本身中处理此问题/添加对此的支持的问题是开放的,但在此期间需要一种解决方案(如果您感兴趣,可以阅读Mockery问题以了解有关替代解决方案及其为何不起作用的详细信息)。

如何使用此功能

WP Test Utils提供了三个新工具来解决此问题(自版本1.1.0起)。

  • Yoast\WPTestUtils\BrainMonkey\makeDoublesForUnavailableClasses( array $class_names ),用于在测试引导文件中使用。
  • Yoast\WPTestUtils\BrainMonkey\TestCase::makeDoublesForUnavailableClasses( array $class_names )Yoast\WPTestUtils\BrainMonkey\TestCase::makeDoubleForUnavailableClass( string $class_name ),用于在测试类中使用。

这些方法可以创建一个或多个“假”测试替身类,允许设置(动态)属性。这些“假”测试替身类实际上是扩展stdClass的不可见类。

这些方法仅适用于测试运行期间不可用的类,对测试运行期间可用的类没有任何影响。

对于设置“假”测试替身的期望,请使用Mockery::mock( 'FakedClass' )

使用引导函数的实现示例

您可以在测试引导文件中一次创建整个测试套件的“假”测试替身,如下所示

<?php
// File: tests/bootstrap.php
require_once dirname( __DIR__ ) . '/vendor/yoast/wp-test-utils/src/BrainMonkey/bootstrap.php';
require_once dirname( __DIR__ ) . '/vendor/autoload.php';

Yoast\WPTestUtils\BrainMonkey\makeDoublesForUnavailableClasses( [ 'WP_Post', 'wpdb' ] );

在测试类中使用这些函数的实现示例

或者,您可以在每个需要它们的测试类中最后时刻创建“假”测试替身。

<?php
namespace PackageName\Tests;

use Mockery;
use WP_Post;
use wpdb;
use Yoast\WPTestUtils\BrainMonkey\TestCase;

class FooTest extends TestCase {
    protected function set_up_before_class() {
        parent::set_up_before_class();
        self::makeDoublesForUnavailableClasses( [ WP_Post::class, wpdb::class ] );
        // or if only one class is needed:
        self::makeDoubleForUnavailableClass( WP_Post::class );
    }

    public function testSomethingWhichUsesWpPost() {
        $wp_post = Mockery::mock( WP_Post::class );
        $wp_post->post_title = 'my test title';
        $wp_post->post_type  = 'my_custom_type';

        $this->assertSame( 'expected', \function_under_test( $wp_post ) );
    }
}

运行与WordPress集成的测试的实用程序

这些实用程序解决的问题

  1. 使用PHPUnit原生模拟功能在PHP 8.0上运行测试,同时测试WordPress 5.6 - 5.8。

    WP 5.6是第一个支持(beta)PHP 8.0的WordPress版本。

    在大多数情况下,对于WordPress集成测试,将加载WordPress Core本地的测试引导文件以设置测试环境,包括数据库。
    然而,WordPress,直到WP 5.9,对PHPUnit 7.5的最大限制很严格,而PHPUnit 9.3是第一个完全支持PHP 8.0的PHPUnit版本,这使得在PHP 8.0上使用WP 5.6 - 5.8进行测试变得有问题的。

    在WordPress 5.6至5.8版本中,这个问题通过将选定的PHPUnit 9.x文件添加到WordPress测试套件,并在PHP 8.0上运行PHPUnit 7.x时加载这些文件(而不是PHPUnit 7.x的本地文件)来解决。
    然而,这种解决方案的实现方式不适用于插件/主题。

    WP Test Utils通过WP集成测试引导工具解决了插件和主题集成测试的问题。

  2. WP 5.9对WP核心测试套件进行了重大更改

    截至WP 5.9,PHPUnit Polyfills软件包成为必需品,测试固件现在需要在snake_case等中进行声明。
    有关WP 5.9中WP核心测试套件更改的详细信息,请参阅Make Core开发者笔记关于这些更改

    这些WP核心测试更改已被部分回溯,一旦插件/主题集成测试套件升级以适应WP 5.9的更改,就可以安全地针对WP trunk5.x5.2 - 5.8)分支运行。

    但是,这里有一个警告

    • 测试旧版WP版本(< 5.2)的插件/主题没有访问polyfills或snake_case固件方法包装器的权限。
    • 同样,直到WordPress 5.8.2被标记,运行针对WP latest的测试也是如此。
    • 同样,对于在回溯提交之前发布的特定WP 5.2 - 5.8次要版本进行的测试也是如此,即WP 5.2.0 - 5.2.12,WP 5.3.0 - 5.3.9,WP 5.4.0 - 5.4.7,WP 5.5.0 - 5.5.6,WP 5.6.0 - 5.6.5,WP 5.7.0 - 5.7.3和WP 5.8.0 - 5.8.1

    WP Test Utils通过提供两个版本的TestCase并加载正确的版本来解决此问题,具体取决于正在运行的测试的WP版本。
    正确的TestCase加载再次通过WP集成测试引导工具处理。

与WordPress集成的测试的基本TestCase

TestCase的功能

  1. 扩展WP原生基础测试用例WP_UnitTestCase,使所有WP核心测试工具对您的集成测试类可用。
  2. 通过PHPUnit Polyfills包与PHPUnit 5.7 - 9.x的跨版本兼容性。
    使用这些polyfills,您可以使用最新的PHPUnit 9.x语法,而无需考虑正在运行的测试的WP版本。
    注意:WP 5.9之前的WordPress核心仍然限制集成测试在PHPUnit 7.5上运行。截至WP 5.9,测试可以在PHPUnit 5.7 - 9.x上跨版本运行。
  3. 能够独立于正在运行的测试的WP版本使用固件方法snake_case包装器。
  4. 用于设置生成输出预期值的辅助函数。

实现示例

<?php
namespace PackageName\Tests;

use Yoast\WPTestUtils\WPIntegration\TestCase;

class FooTest extends TestCase {
    protected function set_up() {
        parent::set_up();
        // Your own additional setup.
    }
}

引导实用函数和自定义自动加载器

WP集成引导工具包括

  • 一个包含用于在插件或主题集成测试的bootstrap文件中使用的实用函数的bootstrap-functions.php文件。
  • 一个自定义自动加载器,该自动加载器将在PHP 8上选择性地自动加载PHPUnit 9原生MockBuilder文件,并处理根据测试运行的WP版本加载正确的TestCase版本。

引导工具假设以下两点

  1. 此软件包作为通过Composer安装的插件依赖项,并位于vendor/yoast/wp-test-utils/目录中。
  2. WordPress核心本地tests/phpunit目录的includes子目录可用。

包含WordPress核心测试框架文件的includes目录的位置将按以下方式确定

  1. 检查是否有一个指向WordPress核心./tests/phpunit目录的WP_TESTS_DIR环境变量;或包含WordPress核心./tests/phpunit目录中includes子目录副本的目录。
  2. 如果没有,检查是否存在一个指向包含WordPress核心根目录的路径的WP_DEVELOP_DIR环境变量,该路径来自git/svn检查。
  3. 如果没有,则检查插件/主题是否安装在了WordPress本身的src/wp-content/plugins/plugin-namesrc/wp-content/themes/theme-name目录中,该包位于src/wp-content/plugins/plugin-name/vendor/yoast/wp-test-utils目录。
  4. 作为最后的手段,将进行典型的WP-CLI scaffold命令设置检查,其中WordPress核心tests/phpunit目录的includes子目录已放置在系统临时目录中。

这些检查与WordPress的典型集成测试设置一致。

👉上述提到的环境变量可以在操作系统级别或从phpunit.xml[.dist]文件中设置。

使用引导实用程序

在集成测试引导文件中将插件/主题连接到WordPress有两种流行的模式。引导实用程序可以用于两者。

要实现使用引导实用程序,请使用与您当前的引导文件最匹配的模式。

实现示例1

use Yoast\WPTestUtils\WPIntegration;

if ( getenv( 'WP_PLUGIN_DIR' ) !== false ) {
    define( 'WP_PLUGIN_DIR', getenv( 'WP_PLUGIN_DIR' ) );
}

$GLOBALS['wp_tests_options'] = [
    'active_plugins' => [ 'plugin-name/main-file.php' ],
];

require_once dirname( __DIR__ ) . '/vendor/yoast/wp-test-utils/src/WPIntegration/bootstrap-functions.php';

/*
 * Bootstrap WordPress. This will also load the Composer autoload file, the PHPUnit Polyfills
 * and the custom autoloader for the TestCase and the mock object classes.
 */
WPIntegration\bootstrap_it();

if ( ! defined( 'WP_PLUGIN_DIR' ) || file_exists( WP_PLUGIN_DIR . '/plugin-name/main-file.php' ) === false ) {
    echo PHP_EOL, 'ERROR: Please check whether the WP_PLUGIN_DIR environment variable is set and set to the correct value. The integration test suite won\'t be able to run without it.', PHP_EOL;
    exit( 1 );
}

实现示例2

use Yoast\WPTestUtils\WPIntegration;

require_once dirname( __DIR__ ) . '/vendor/yoast/wp-test-utils/src/WPIntegration/bootstrap-functions.php';

$_tests_dir = WPIntegration\get_path_to_wp_test_dir();

// Get access to tests_add_filter() function.
require_once $_tests_dir . 'includes/functions.php';

/**
 * Callback to manually load the plugin
 */
function _manually_load_plugin() {
    require_once __DIR__ . '/relative/path/to/main-file.php';
}

// Add plugin to active mu-plugins to make sure it gets loaded.
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );

/*
 * Bootstrap WordPress. This will also load the Composer autoload file, the PHPUnit Polyfills
 * and the custom autoloader for the TestCase and the mock object classes.
 */
WPIntegration\bootstrap_it();

测试辅助函数

Yoast\WPTestUtils\Helpers\EscapeOutputHelper 特性

PHPUnit自带了expectOutputString()(精确字符串)和expectOutputRegex()(正则匹配)方法,但有时你需要更多的灵活性。

一个典型的模式是检查生成的输出是否包含某些子字符串。输出与预期不符的典型原因是行结束符不匹配。

EscapeOutputHelper特质添加了以下功能来解决这些问题:

  • expectOutputContains( $expected, $ignoreEolDiff = true )用于验证生成的输出是否包含某个子字符串。与PHPUnit原生方法类似,一个测试中只能设置一个这样的预期。如果测试需要检查生成的输出是否包含多个不同的子字符串,请重构测试以使用数据提供程序逐个向测试传递一个子字符串。
  • normalizeLineEndings( $output )旨在与PHPUnit原生的setOutputCallback()方法一起使用,以便在将输出预期与实际输出进行比较之前,在比较输出预期之前对实际输出中的行结束符进行归一化。

👉此特质自动对基于BrainMonkey或WPIntegration TestCase的测试类可用,这些测试类包含在WP Test Utils中。

贡献

欢迎对此项目做出贡献。克隆存储库,从develop分支,进行更改,提交更改并提交拉取请求。

如果您不确定您提出的更改是否会受到欢迎,请首先打开一个问题来讨论您的建议。

许可

此代码在BSD-3-Clause License下发布。