cyruscollier/wp-test

WordPress 单元和集成测试框架

1.4 2020-09-26 00:47 UTC

This package is auto-updated.

Last update: 2024-09-26 09:43:45 UTC


README

WP Test

WP Test 是一个库,用于快速轻松地设置和执行 WordPress 单元和集成测试。它允许您在任何新的或现有的 WordPress 主题、插件或完整网站项目上初始化自动化测试套件。

基本功能

  • 使用与 WordPress 核心测试套件相同的 PHPUnit 基础框架,这要归功于 框架的镜像仓库
  • CLI 命令用于创建和自定义项目内部核心测试框架所需的配置文件。
  • WP_UnitTestCase 的子类,具有额外的便捷方法和自定义断言
  • 增强的 PHPUnit 启动过程,可自动激活项目主题和插件
  • 除了单元测试代码外,还有一个针对外部服务(Stripe、Facebook 等)的集成测试的单独测试组

高级 TDD 模式

通过 Composer 安装

将 Composer 包作为开发依赖项添加到您的 WordPress 项目中

composer require cyruscollier/wp-test --dev

运行初始化控制台命令。如果您的系统 $PATH 已经包括您本地的 Composer bin 目录,则可以省略完整路径。

./vendor/bin/wp-test init

按照控制台中的提示配置您的测试环境。

  1. 选择单元测试架构
  2. 项目命名空间
  3. 源文件路径
  4. 单元测试路径
  5. 集成测试路径(仅高级 TDD)
  6. WordPress 核心目录路径,相对于项目根目录
  7. wp-content 目录路径,相对于项目根目录
  8. 活动主题

安装本地 MySQL 数据库

对于 PHPUnit/WordPress 核心运行环境是必需的。根据您的操作系统,有安装 mysql 服务器的许多方法。这里有两个推荐方法

Homebrew(仅限 Mac OS)

brew update
brew install mysql@5.7
brew link mysql@5.7 --force
brew services start mysql@5.7
mysql -uroot -e "CREATE DATABASE IF NOT EXISTS wp_tests CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

Docker

docker run -d -p 3306:3306 --name wp_tests -e MYSQL_ALLOW_EMPTY_PASSWORD=yes -e MYSQL_DATABASE=wp_tests mysql:5.7

Docker 需要与 mysql 容器建立 TCP 连接,因此在 wp-tests-config.php 中,将 DB_HOST 更改为 127.0.0.1

VM(vagrant 等)或 Docker mysql 服务器也可以使用,但服务器必须设置为接受来自任何主机的请求,使用配置的用户通过开放的端口(如 3306)进行请求,或者必须直接在环境中的测试运行命令执行,而不是在主机机的终端中。

用法

在项目根目录下运行PHPUnit

./vendor/bin/phpunit

或者运行watcher,以便在您的代码有任何更改时重新运行测试

./vendor/bin/phpunit-watcher watch

对于集成测试

./vendor/bin/phpunit --group integration

完整的PHPUnit文档:https://phpunit.readthedocs.io/en/7.5/

如果使用高级TDD模式,运行phpspec

./vendor/bin/phpspec run

或者运行watcher,以便在您的代码有任何更改时重新运行测试

./vendor/bin/phpspec-watcher watch

完整的phpspec文档:https://www.phpspec.net/en/stable/manual/introduction.html

phpunit.xml中的配置选项

编写WordPress PHPUnit测试

类设置

对于每个PHPUnit测试类,扩展WordPress的WP_UnitTestCase类的子类WPTest\Test\TestCase,它本身扩展了PHPUnit的PHPUnit\Framework\TestCase类。

如果您在测试类中添加了setup()teardown()方法,您必须确保在其中调用父方法。WordPress在setup()期间启动数据库事务并准备全局状态,在teardown()期间回滚事务并重置全局和其他状态回基线。此设置和清理过程确保每个测试方法都以期望的干净WordPress环境开始。

工厂与管理状态

尽管您可以完全访问整个WordPress核心API,但测试环境中的工厂对于轻松创建帖子、术语、用户和其他实体非常有用。对于任何未提供的数据的数据库字段,将添加占位数据。例如,对于Post Factory,您可以在一行中创建并返回一个包含所需自定义数据的新帖子

/* Test */
$event_post = $this->factory()->post->create_and_get([
    'post_type' => 'event',
    'meta_input' => [
        'event_start' => '2021-01-01 18:00:00',
        'event_end' => '2021-01-01 21:00:00'
    ]
]);

与大多数单元测试一样,在执行测试方法之前设置所需的状态。对于WordPress,这通常意味着以下一项或多项

  1. 创建WordPress实体:帖子、术语、用户等。
  2. 向新创建的实体添加元数据或将术语添加到帖子中。
  3. 设置核心选项或自定义选项。
  4. 手动填充PHP超全局变量:$_GET$_POST等。
  5. 调用设置各种全局状态的WordPress函数:set_current_screen()wp_set_current_user()等。
  6. 手动操作WordPress全局变量:$post$wp_query等。

然后执行要测试的函数/方法,并使用PHPUnit断言对数据库或其他状态如何变化进行断言。WP Test在WordPress提供的基础上提供了几个额外的WordPress特定断言。

由于WordPress数据库和全局状态在整个测试方法期间保持不变,有时在单个测试方法中测试函数/方法的几个不同变体或阶段可以是有帮助的技术,否则您必须在一个单独的方法中重新创建相同的初始或结果状态,这会重复工作并减慢测试套件。但请谨慎使用,因为您仍然希望在单个测试方法中只测试代码库的一个明确行为。

模拟HTTP请求

在单元测试中制作真实的HTTP请求会使测试套件变慢且脆弱,因此最好模拟请求和响应。假设您的代码正在使用wp_remote_get()wp_remote_post()或类似包装WP_Http的代码,请使用pre_http_request过滤器对预期的输入进行断言,并返回一个包含body数组的虚假响应数组。您还应该模拟返回一个WP_Error对象作为响应,以便您的代码可以相应地处理它

/* Source */
function make_api_request($parameter)
{
    $response = wp_remote_post('https://yourapi.com/path/to/resource/', [
        'body' => [
           'keyword' => $parameter,
           'apikey' => 'your api key'
        ]
    ]);
    if (is_wp_error($response)) {
        throw new Exception($response->get_error_message());
    }
    return json_decode($response['body']);
);
/* Test */
add_filter('pre_http_request', function($pre, $parsed_args, $url) {
    $this->assertEquals('https://yourapi.com/path/to/resource/', $url);
    $this->assertContains(['method' => 'POST', 'body' => [
        'keyword' => 'test keyword',
        'apikey' => 'your api key'
    ]], $parsed_args);
    return ['body' => json_encode(['message' => 'success']])];
}, 10, 3);
$this->assertEquals(['message' => 'success'], make_api_request('test keyword'));
/* Test */
add_filter('pre_http_request', function($pre, $parsed_args, $url) {
    $this->assertContains(['method' => 'POST', 'body' => [
        'keyword' => 'test keyword',
        'apikey' => 'invalid api key'
    ]], $parsed_args);
    return new WP_Error('http_request_failed', 'Invalid API Key');
}, 10, 3);
$this->expectException(Exception::class);
$this-expectExceptionMessage('Invalid API Key');
make_api_request('test keyword');

模拟重定向

大多数单元测试都是针对底层代码,通常不会处理像重定向这样的高级应用逻辑。然而,如果您决定对像表单提交和重定向这样的应用逻辑进行单元测试,您需要能够在不实际输出Location头的情况下验证重定向URL。输出该头会在测试环境中触发“Headers Already Sent”警告,因为这是一个长时间运行的PHP进程,它没有向浏览器提供响应。大多数对wp_redirect()的调用都会紧接着使用exit/die,在测试环境中这也不可行,因为它将终止进程。由于成功的wp_redirect()将返回true,因此在退出之前使用此单行条件检查wp_redirect()的返回值。

/* Source */
function form_redirect()
{
    $redirect_url = 'https://yoursite.com/form-success-page';
    return wp_redirect($redirect_url) && exit;
}

然后在您的测试中,添加一个类似模拟HTTP请求的过滤器,返回false而不是true,从而避免输出头和退出。

/* Test */
add_filter('wp_redirect', function($url) {
    $this->assertEquals('https://yoursite.com/form-success-page', $url);
    return false;
});
$this->assertFalse(form_redirect());

测试输出

测试PHP输出(如echoprintf()等)并不是WordPress特有的,但它出现得更多,因为API的大部分要求输出被echo输出而不是简单地返回一个值。为了测试这些场景,请使用输出缓冲。您可以直接使用ob_get_clean()捕获输出,并使用WP Test的assertHTMLEquals()断言将其与预期的HTML片段进行比较,或者如果用于准备输出的数据更有用或更容易处理,只需在输出后返回它并对其进行断言。

/* Source */
function output_something()
{
    $data = ['thing 1', 'thing 2'];
    printf('<ul><li>%s</li></ul>', implode('</li><li>', $data));
    return $data;
}
/* Test */
ob_start();
output_something();
$output = ob_get_clean();
$this->assertHTMLEquals('<ul><li>thing 1</li><li>thing 2</li></ul>', $output);
// or
ob_start();
$data = output_something();
ob_get_clean();
$this->assertEquals(['thing 1', 'thing 2'], $data);

测试钩子

测试钩子涉及两个部分。首先,验证钩子已经添加并且带有其分配的回调函数。其次,要么触发该钩子,要么直接执行该函数,并根据其返回值、状态变化等进行断言。确保钩子的回调是一个命名函数或方法,而不是匿名函数,这样就可以在assertHasAction()/assertHasFilter()中进行引用。对于动作回调,返回一些有用的数据以便进行断言是有帮助的,尽管这个返回值在实时执行中没有被使用。

/* Source */
function perform_custom_action()
{
    // ...
    $data = ['thing 1', 'thing 2'];
    // ...
    return $data;
}

add_action('init', 'perform_custom_action');
/* Test */
$this->assertHasAction('init', 'perform_custom_action');
$this->assertEquals(['thing 1', 'thing 2'], perform_custom_actio());

变更日志

v1.4 - 从WordPress核心依赖中切换到WP PHPUnit。移除Composer仓库要求。

v1.3 - 增加了在隔离的主题或插件项目中使用的能力,修复phpunit.xml,扩展和改进了文档。

v1.2 - 改进了设置并修复了问题,添加了wp-test reset命令,内部重构,更新了readme。

v1.1 - 修订了wp-test init命令以允许选择是否使用phpspec进行单元测试。改进和修复了配置模板。添加了phpunit-watcher。添加了readme。

v1.0.2 - 修复phpunit.xml模板以与WP核心对齐

v1.0.1 - 新的WP Core仓库,修复配置路径和PHPUnit版本以与WP兼容

v1.0 - 初版