woocommerce/.ai/skills/woocommerce-backend-dev/zh-cn/unit-tests.md
2026-04-15 10:18:01 +08:00

10 KiB
Raw Permalink Blame History

单元测试规范

目录

完整测试文件模板

创建新测试文件时请使用此模板。它展示了所有约定规范的应用:

<?php
declare( strict_types = 1 );

namespace Automattic\WooCommerce\Tests\Internal\Admin;

use Automattic\WooCommerce\Internal\Admin\OrderProcessor;
use WC_Unit_Test_Case;

/**
 * OrderProcessor 类的测试。
 */
class OrderProcessorTest extends WC_Unit_Test_Case {

	/**
	 * 被测系统。
	 *
	 * @var OrderProcessor
	 */
	private $sut;

	/**
	 * 设置测试固件。
	 */
	public function setUp(): void {
		parent::setUp();
		$this->sut = new OrderProcessor();
	}

	/**
	 * 清理测试固件。
	 */
	public function tearDown(): void {
		parent::tearDown();
	}

	/**
	 * @testdox 当订单有效时应返回 true。
	 */
	public function test_returns_true_for_valid_order(): void {
		$order = wc_create_order();

		$result = $this->sut->is_valid( $order );

		$this->assertTrue( $result, '有效订单应返回 true' );
	}

	/**
	 * @testdox 当订单 ID 为负数时应抛出异常。
	 */
	public function test_throws_exception_for_negative_order_id(): void {
		$this->expectException( \InvalidArgumentException::class );

		$this->sut->process( -1 );
	}
}

关键元素

元素 要求
declare( strict_types = 1 ) 在文件开始处必需
命名空间 匹配源位置:Automattic\WooCommerce\Tests\{path}
基类 继承 WC_Unit_Test_Case
SUT 变量 使用 $sut 并附带文档块 "The System Under Test."
测试文档块 使用 @testdox 并以 . 结尾的句子
返回类型 为测试方法使用 void
断言消息 包含有助于调试失败的上下文

测试文件命名和位置

来源 测试 样板
includes/ tests/php/includes/{路径}/class-wc-{名称}-test.php 添加 -test 后缀
src/ tests/php/src/{路径}/{名称}Test.php 追加 Test (无连字符)

测试类:与来源类同名 + _TestTest 后缀,继承 WC_Unit_Test_Case

被测系统变量

使用 $sut 并添加文档块注释 "The System Under Test."

/**
 * The System Under Test.
 *
 * @var OrderProcessor
 */
private $sut;

测试方法文档

当添加或修改单元测试方法时,描述测试的文档块部分必须以 @testdox 开头。评论应以 . 结尾以符合代码检查规则。

示例:

/**
 * @testdox Should return true when order is valid.
 */
public function test_returns_true_for_valid_order() {
    // ...
}

/**
 * @testdox Should throw exception when order ID is negative.
 */
public function test_throws_exception_for_negative_order_id() {
    // ...
}

测试中的评论

避免过度注释测试。 测试名称和断言消息应能解释意图。

良好 - 不言自明:

/**
 * @testdox 当订单状态为草稿时应返回 true。
 */
public function test_returns_true_for_draft_orders() {
    $order = $this->create_draft_order();

    $result = $this->sut->can_delete( $order );

    $this->assertTrue( $result, '草稿订单应可删除' );
}

避免 - 过度注释:

/**
 * @testdox 当订单状态为草稿时应返回 true。
 */
public function test_returns_true_for_draft_orders() {
    // 创建一个草稿订单
    $order = $this->create_draft_order();

    // 调用我们正在测试的方法
    $result = $this->sut->can_delete( $order );

    // 验证结果为 true
    $this->assertTrue( $result, '草稿订单应可删除' );
}

避免 - Arrange/Act/Assert 结构注释:

// 不要添加这些结构注释
// Arrange
$order = $this->create_draft_order();

// Act
$result = $this->sut->can_delete( $order );

// Assert
$this->assertTrue( $result );

应使用空行进行视觉分隔。测试结构应是不言自明的。

在测试中评论确实有用的情况:

  • 解释复杂的测试设置:// 通过...模拟竞态条件
  • 记录已知问题:// WordPress 核心 bug #12345 的变通方案
  • 澄清业务规则:// 支付处理器要求 24 小时保留期

测试配置

测试配置文件:phpunit.xml

例子:支付扩展建议测试

PaymentsExtensionSuggestionsTest 类展示了针对国家特定功能进行良好测试的实践。

已使用的关键字样板

  1. 数据驱动测试 使用 PHPUnit 数据提供者
  2. 扩展计数验证 为不同的商户类型
  3. 清晰的测试组织 按商户类型(在线/离线)

测试结构示例

class PaymentsExtensionSuggestionsTest extends WC_Unit_Test_Case {
    /**
     * 被测系统。
     *
     * @var PaymentsExtensionSuggestions
     */
    private $sut;

    public function setUp(): void {
        parent::setUp();
        $this->sut = new PaymentsExtensionSuggestions();
    }

    /**
     * @testdox 应根据国家/地区为在线商家返回正确的扩展数量
     * @dataProvider online_merchant_country_data
     */
    public function test_get_country_extensions_count_for_online_merchants(
        string $country_code,
        int $expected_count
    ) {
        $merchant = array(
            'country'       => $country_code,
            'selling_venues' => 'online',
        );

        $result = $this->sut->get_country_extensions_count( $merchant );

        $this->assertSame(
            $expected_count,
            $result,
            "Expected {$expected_count} extensions for online merchant in {$country_code}"
        );
    }

    /**
     * 在线商家测试的数据提供者。
     *
     * @return array
     */
    public function online_merchant_country_data() {
        return array(
            'United States'    => array( 'US', 5 ),
            'United Kingdom'   => array( 'GB', 4 ),
            'Canada'           => array( 'CA', 3 ),
            'Australia'        => array( 'AU', 3 ),
            // ... 更多国家/地区
        );
    }

    /**
     * @testdox 应根据国家/地区为线下商家返回正确的扩展数量
     * @dataProvider offline_merchant_country_data
     */
    public function test_get_country_extensions_count_for_offline_merchants(
        string $country_code,
        int $expected_count
    ) {
        $merchant = array(
            'country'        => $country_code,
            'selling_venues' => 'offline',
        );

        $result = $this->sut->get_country_extensions_count( $merchant );

        $this->assertSame(
            $expected_count,
            $result,
            "Expected {$expected_count} extensions for offline merchant in {$country_code}"
        );
    }

    /**
     * 线下商家测试的数据提供者。
     *
     * @return array
     */
    public function offline_merchant_country_data() {
        return array(
            'United States'  => array( 'US', 2 ),
            'United Kingdom' => array( 'GB', 1 ),
            'Canada'         => array( 'CA', 1 ),
            // ... 更多国家/地区
        );
    }
}

支付扩展测试的重要注意事项

处理支付扩展建议时:

  1. 扩展数量必须与实现匹配src/Internal/Admin/Suggestions/PaymentsExtensionSuggestions.php
  2. 新增国家时 在实现中更新测试文件中的两个数据提供器
  3. 测试按商户类型分开(在线与离线)因为它们有不同的扩展数量
  4. 数据提供器使用描述性关键字(国家名称)以获得更好的测试输出

模拟 WooCommerce 日志记录器

当测试使用 wc_get_logger()(直接或通过 SafeGlobalFunctionProxy::wc_get_logger())的代码时,使用 woocommerce_logging_class 过滤器注入一个模拟日志记录器。

为何采用过滤方法?

  • register_legacy_proxy_function_mocks 无法拦截 SafeGlobalFunctionProxy 调用
  • 传递对象(而非类名字符串)会绕过 wc_get_logger() 的内部缓存

创建模拟日志记录器

模拟日志记录器必须实现 WC_Logger_Interface。创建一个匿名类,其中包含用于跟踪调用的公开数组($debug_calls$warning_calls 等),并实现所有界面方法(addlogdebuginfowarningerroremergencyalertcriticalnotice)。

使用模拟日志记录器

public function test_logs_warning_for_invalid_input(): void {
    $fake_logger = $this->create_fake_logger();

    // 通过筛选器注入 - 传递对象可绕过缓存。
    add_filter(
        'woocommerce_logging_class',
        function () use ( $fake_logger ) {
            return $fake_logger;
        }
    );

    $this->sut->process_input( 'invalid-value' );

    $this->assertCount( 1, $fake_logger->warning_calls );

    remove_all_filters( 'woocommerce_logging_class' ); // 务必清理。
}

关键要点

方面 详情
筛选名称 woocommerce_logging_class
返回值 对象实例(非类名字符串)
界面 必须实现 WC_Logger_Interface
清理 测试后总是调用 remove_all_filters()

参考

完整实现请参见 PaymentGatewayTest.php:create_fake_logger()

通用测试最佳实践

  1. 进行更改后始终运行测试 以验证功能
  2. 在开发过程中使用特定的测试过滤器(参见 woocommerce-dev-cycle 技能中的 running-tests.md
  3. 编写描述性的测试名称,以说明正在测试的内容
  4. 使用数据提供器 来测试具有相同逻辑的多个场景
  5. 包含有用的断言消息,以便在测试失败时进行调试
  6. 测试成功和失败两种情况
  7. 模拟外部依赖项数据库、API 调用等)