如何为 WordPress 插件添加自动化单元测试

原文:How to add automated unit tests to your WordPress plugin

作者:David Perez

原文发布日期:2025 年 12 月 16 日

来源:WordPress Developer Blog

WordPress 插件会面对不同的 WordPress 版本、PHP 配置、主题和插件组合。代码在开发者自己的机器上正常,不代表每位用户的站点都没有问题;后续改动也可能意外破坏原有功能。把测试纳入开发流程,能在发布前发现回归,并让每次改动都有可重复的检查依据。

本文沿用 David Perez 的 Composer 路线,说明如何建立 WordPress 集成测试、使用 PHPUnit 编写断言,再把测试接入 GitHub Actions。测试不可能覆盖所有生产环境组合,但可以稳定检查关键函数、钩子和内容类型在预期条件下的行为。

单元测试与其他测试

单元测试针对一个函数或方法,提供不同输入并检查结果是否符合预期。除了有效输入,也应考虑无效、缺失或边界数据,避免异常输入造成致命错误。

  • 单元测试:隔离检查一小段代码,不依赖外部元素。
  • 集成测试:检查不同部分能否协作,例如插件与 WordPress 数据库。本文使用真实 WordPress 测试环境。
  • 功能测试:从用户角度检查整个系统的行为。

测试可以提前发现回归,提升维护信心,也帮助协作者了解改动影响。测试用例还会记录函数在特定数据下应有的行为,因而能作为代码行为的参考文档。

准备本地测试环境

WordPress 集成测试需要干净的 WordPress 环境以及相应依赖。常见方法有 NPM 与 Docker 的 wp-env,或 Composer。本文选择 Composer,作为教程中的简明做法。

请先准备 PHP、MySQL、SVN 和 WordPress CLI(WP-CLI)。MySQL 中需要有供测试使用的数据库,并准备好安装脚本所需的数据库用户名、密码和主机。测试环境安装会用 SVN 下载文件;WP-CLI 用于生成插件测试文件。

原文环境说明中的数据库名写作 wordpres_tests,后续安装命令则使用 wordpress_test。以下采用后续命令中的拼法;请确保本机数据库与脚本传入的值一致。原文账户示例为 root,项目应按自己的本地配置修改。

生成测试脚手架

用 WP-CLI 创建初始测试文件,将 plugin-name 替换成插件名:

wp scaffold plugin-tests plugin-name --ci=github

命令会生成以下常见文件:

  • .github/workflows/phpunit.yml:GitHub Actions 工作流;
  • bin/install-wp-tests.sh:准备测试环境的 Bash 脚本;
  • phpunit.xml.dist:PHPUnit 配置;
  • tests/bootstrap.php:加载 WordPress、配置和插件的引导文件;
  • tests/test-sample.php:示例测试。

这些文件提供起点,之后还需按项目结构调整测试目录、引导常量和 CI 配置。

安装 Composer 依赖并配置命令

在仓库中安装 WordPress PHPUnit 测试环境和兼容层:

composer require --dev yoast/phpunit-polyfills:^1.0 wp-phpunit/wp-phpunit:^6.3

wp-phpunit/wp-phpunit 是社区维护的 WordPress 测试环境实现,可省去手动克隆整个 WordPress 核心的步骤。yoast/phpunit-polyfills 为不同 PHPUnit 和 PHP 版本提供兼容层。

在 .gitignore 中忽略测试结果缓存和 Composer 依赖目录:

.phpunit.result.cache
vendor/

在 composer.json 的 scripts 中添加测试和安装命令。以下保留原文的数据库、账户、地址和 WordPress 版本示例:

"scripts": {
    "test": "phpunit",
    "test-install": "bash bin/install-wp-tests.sh wordpress_test root 'root' 127.0.0.1 latest"
}

test-install 参数依次指定数据库名、用户名、密码、主机和 WordPress 版本;示例主机为 127.0.0.1。按本机数据库配置调整这些值。接着安装测试环境:

composer test-install

原文建议检查 phpunit.xml.dist,从 <directory> 行移除 prefix="test-",以避免某些版本下的冲突;再把测试集中到 tests/Unit/。配置示例:

<testsuites>
  <testsuite name="testing">
    <directory suffix=".php">./tests/Unit/</directory>
  </testsuite>
</testsuites>

把原来的示例测试文件移动到 tests/Unit/。目录名称可以按项目调整,但需与 PHPUnit 配置一致。

在 tests/bootstrap.php 开头定义插件和测试数据目录,并在环境未提供 WP_CORE_DIR 时从环境变量或临时目录确定路径:

define( 'TESTS_PLUGIN_DIR', dirname( __DIR__ ) );
define( 'UNIT_TESTS_DATA_PLUGIN_DIR', TESTS_PLUGIN_DIR . '/tests/Data/' ); // 可按项目调整。

// 如果 WP_CORE_DIR 尚未定义,则使用环境变量或临时目录。
if ( ! defined( 'WP_CORE_DIR' ) ) {
    $_wp_core_dir = getenv( 'WP_CORE_DIR' );
    if ( ! $_wp_core_dir ) {
        $_wp_core_dir = rtrim( sys_get_temp_dir(), '/\\' ) . '/wordpress';
    }
    define( 'WP_CORE_DIR', $_wp_core_dir );
}

运行第一个测试

准备完成后,在项目根目录运行:

composer test

原文记录的示例结果是 PHPUnit 9.6.24 执行 1 项测试、1 条断言并通过。这是原文当时的输出示例,不代表本文在当前环境运行过测试。

如果出现 Could not find /wordpress-tests-lib/includes/functions.php,通常说明临时 WordPress 安装不完整。原文建议删除安装脚本报告的临时目录中的 wordpress-tests-lib 与 wordpress,再重新安装。以下保留原文示例路径;执行前确认该目录确实是本项目的临时测试环境:

rm -rfv /var/folders/kk/6287m8gj09xdkt2zgz432zhr0000gn/T/wordpress-tests-lib/
rm -rfv /var/folders/kk/6287m8gj09xdkt2zgz432zhr0000gn/T/wordpress/
composer test-install

使用 PHPUnit 断言

PHPUnit 提供多种断言,用于比较实际值与预期值。常用方法有:

  • assertTrue($condition)、assertFalse($condition):检查条件真假;
  • assertEquals($expected, $actual):宽松比较;
  • assertSame($expected, $actual):严格比较类型和值;
  • assertNull()、assertNotNull():检查是否为 null;
  • assertEmpty()、assertNotEmpty():检查是否为空;
  • assertCount()、assertContains():检查数量或成员;
  • assertInstanceOf()、assertIsArray()、assertIsString()、assertIsInt():检查对象或类型;
  • assertGreaterThan()、assertLessThan():比较数值。

原文用求和函数演示如何验证正常输入:

function plunit_sum( $a, $b ) {
    return $a + $b;
}

public function test_sum_without_errors() {
    $sum = plunit_sum( 4, 2 );
    $this->assertEquals( 6, $sum );
}

也要为错误或缺失输入编写测试。原文示例把字符串 'hello' 和数字 2 传入求和函数,并期望结果为 0。要让断言成立,函数需要实现相应的输入校验或安全处理;单纯的直接加法实现并未展示这种逻辑。运行测试仍使用:

composer test

测试自定义文章类型

假设插件注册了名为 book 的自定义文章类型(CPT),可检查它是否注册、是否公开、是否出现在管理界面,以及能否创建对应文章。原文示例使用 WP_UnitTestCase 和 WordPress 测试工厂:

<?php
/**
 * Integration tests for the Book custom post type.
 */
class Test_Book_CPT extends WP_UnitTestCase {

    public function setUp(): void {
        parent::setUp();
        // Make sure our CPT is registered before each test.
        mtp_register_cpt_book();
        flush_rewrite_rules();
    }

    public function test_book_post_type_is_registered() {
        $this->assertTrue( post_type_exists( 'book' ), 'The "book" post type should be registered.' );
    }

    public function test_book_post_type_is_public() {
        $post_type_obj = get_post_type_object( 'book' );

        $this->assertNotNull( $post_type_obj );
        $this->assertTrue( $post_type_obj->public );
        $this->assertTrue( $post_type_obj->show_ui );
    }

    public function test_can_create_book_post() {
        $post_id = self::factory()->post->create(
            array(
                'post_type'  => 'book',
                'post_title' => 'Test Book',
            )
        );

        $this->assertIsInt( $post_id );
        $this->assertSame( 'book', get_post_type( $post_id ) );
        $this->assertSame( 'Test Book', get_the_title( $post_id ) );
    }
}

继承 WP_UnitTestCase 后,可以使用 PHPUnit 断言和 WordPress 测试工具,例如创建文章、用户和分类的工厂。基类也会让测试运行于 WordPress 测试环境。

setUp() 在每项测试前执行:先调用 parent::setUp(),再确保插件注册 CPT,并刷新重写规则,使测试从一致状态开始。若测试修改了其他全局状态,可视需要用 tearDown() 清理;测试工厂创建的数据通常由测试框架负责清理。

三项测试分别检查:post_type_exists( 'book' ) 为真;文章类型对象存在且 public、show_ui 为真;测试工厂创建的文章 ID 是整数、类型为 book,标题为 Test Book。这些断言能发现 slug 拼写错误、可见性标志错误或注册过程未运行等问题。

安装并加载插件依赖

若被测插件依赖其他插件,需要把依赖安装到测试环境,并从测试引导文件加载。原文以 WooCommerce 为例:在 bin/install-wp-tests.sh 的 install_wp 之前定义安装函数,并在 WordPress 安装完成后调用它:

# Installs WooCommerce plugin in the test environment
install_woocommerce() {
    local PLUGIN_DIR="$WP_CORE_DIR/wp-content/plugins"
    mkdir -p "$PLUGIN_DIR"
    WOOCOMMERCE_URL="https://downloads.wordpress.org/plugin/woocommerce.zip"
    download "$WOOCOMMERCE_URL" "$TMPDIR/woocommerce.zip"
    unzip -q "$TMPDIR/woocommerce.zip" -d "$TMPDIR/"
    rm -rf "$PLUGIN_DIR/woocommerce"
    mv "$TMPDIR/woocommerce" "$PLUGIN_DIR/woocommerce"
    echo "WooCommerce plugin installed successfully."
}

之后,在 tests/bootstrap.php 的 _manually_load_plugin 函数中加载 WooCommerce:

// Load WooCommerce first from the standard WordPress plugins directory.
require_once WP_CORE_DIR . '/wp-content/plugins/woocommerce/woocommerce.php';

其他依赖插件也应在安装脚本中准备好,并在引导代码中加载。

配置 GitHub Actions

原文建议检查 GitHub Actions 工作流里的主分支配置,让拉取请求触发测试:

on:
  pull_request:
    branches:
      - trunk

将 trunk 替换为仓库实际使用的主分支。脚手架文件清单中工作流名为 phpunit.yml,本节示例使用 testing.yml;应编辑仓库中实际存在的工作流文件。

原文还建议检查 PHP 配置,并显式安装 SVN,因为 GitHub Actions 环境默认不包含 SVN:

- name: Setup PHP
  uses: shivammathur/setup-php@v2
  with:
    php-version: ${{ matrix.php-version }}
    tools: phpunit-polyfills:1.1
    extensions: mbstring, xml, zip, intl, pdo, mysql
    coverage: none

- name: Install SVN
  run: sudo apt-get update && sudo apt-get install -y subversion

PHP 版本、扩展和工具应与项目依赖及测试环境一致。配置完成后,向指定主分支发起拉取请求时,CI 会自动运行测试。

小结

从 WP-CLI 脚手架开始,使用 Composer 安装测试依赖,再配置数据库、PHPUnit 和引导文件,就能在本机运行 WordPress 插件测试。接下来为关键函数和功能补充断言,并把测试集成到 GitHub Actions,让每次拉取请求自动触发检查。

示例从自定义文章类型入手,也可以继续覆盖插件中的函数、类和钩子。David Perez 在原文末尾感谢 @milana_cap、@bph、@greenshady、@juanmaguitar 和 @areziaal 提供审阅反馈。


来源与作者信息:David Perez,《How to add automated unit tests to your WordPress plugin》,WordPress Developer Blog,2025-12-16。 原文链接:https://developer.wordpress.org/news/2025/12/how-to-add-automated-unit-tests-to-your-wordpress-plugin/

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
相关推荐
评论 抢沙发

请登录后发表评论

    暂无评论内容