cyruscollier / wp-test
WordPress 单元和集成测试框架
Requires
- phpunit/phpunit: ^7.5.18
- symfony/console: ^5.1|^4.4
- symfony/yaml: ^5.1|^4.4
- wp-phpunit/wp-phpunit: ^5.5
README
WP Test 是一个库,用于快速轻松地设置和执行 WordPress 单元和集成测试。它允许您在任何新的或现有的 WordPress 主题、插件或完整网站项目上初始化自动化测试套件。
基本功能
- 使用与 WordPress 核心测试套件相同的 PHPUnit 基础框架,这要归功于 框架的镜像仓库。
- CLI 命令用于创建和自定义项目内部核心测试框架所需的配置文件。
WP_UnitTestCase
的子类,具有额外的便捷方法和自定义断言- 增强的 PHPUnit 启动过程,可自动激活项目主题和插件
- 除了单元测试代码外,还有一个针对外部服务(Stripe、Facebook 等)的集成测试的单独测试组
高级 TDD 模式
- 使用 phpspec 设计和单元测试无依赖的领域模型
- 使用上述基本设置仅驱动集成测试,将 WordPress 本身视为规格中的外部依赖
- 将 WP 类的最低限度的集合包含到 phpspec 中,以方便常见 spec 使用案例,而无需运行 WordPress
- 包含大多数常见 WP 函数的存根
- 使用 PhpSpec - PHP-Mock 在 spec 中按需模拟其他 WP 函数
通过 Composer 安装
将 Composer 包作为开发依赖项添加到您的 WordPress 项目中
composer require cyruscollier/wp-test --dev
运行初始化控制台命令。如果您的系统 $PATH
已经包括您本地的 Composer bin 目录,则可以省略完整路径。
./vendor/bin/wp-test init
按照控制台中的提示配置您的测试环境。
- 选择单元测试架构
- 项目命名空间
- 源文件路径
- 单元测试路径
- 集成测试路径(仅高级 TDD)
- WordPress 核心目录路径,相对于项目根目录
- wp-content 目录路径,相对于项目根目录
- 活动主题
安装本地 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,这通常意味着以下一项或多项
- 创建WordPress实体:帖子、术语、用户等。
- 向新创建的实体添加元数据或将术语添加到帖子中。
- 设置核心选项或自定义选项。
- 手动填充PHP超全局变量:
$_GET
、$_POST
等。 - 调用设置各种全局状态的WordPress函数:
set_current_screen()
、wp_set_current_user()
等。 - 手动操作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输出(如echo
、printf()
等)并不是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 - 初版