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

362 lines
No EOL
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 单元测试规范
## 目录
- [完整测试文件模板](#完整测试文件模板)
- [测试文件命名和位置](#测试文件命名和位置)
- [被测系统变量](#被测系统变量)
- [测试方法文档](#测试方法文档)
- [测试中的评论](#测试中的评论)
- [测试配置](#测试配置)
- [示例:支付扩展建议测试](#示例支付扩展建议测试)
- [模拟 WooCommerce 日志记录器](#模拟-woocommerce-日志记录器)
- [通用测试最佳实践](#通用测试最佳实践)
## 完整测试文件模板
创建新测试文件时请使用此模板。它展示了所有约定规范的应用:
```php
<?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` (无连字符)|
测试类:与来源类同名 + `_Test``Test` 后缀,继承 `WC_Unit_Test_Case`
## 被测系统变量
使用 `$sut` 并添加文档块注释 "The System Under Test."
```php
/**
* The System Under Test.
*
* @var OrderProcessor
*/
private $sut;
```
## 测试方法文档
当添加或修改单元测试方法时,描述测试的文档块部分必须以 `@testdox` 开头。评论应以 `.` 结尾以符合代码检查规则。
**示例:**
```php
/**
* @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() {
// ...
}
```
## 测试中的评论
**避免过度注释测试。** 测试名称和断言消息应能解释意图。
**良好 - 不言自明:**
```php
/**
* @testdox 当订单状态为草稿时应返回 true。
*/
public function test_returns_true_for_draft_orders() {
$order = $this->create_draft_order();
$result = $this->sut->can_delete( $order );
$this->assertTrue( $result, '草稿订单应可删除' );
}
```
**避免 - 过度注释:**
```php
/**
* @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 结构注释:**
```php
// 不要添加这些结构注释
// 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. **清晰的测试组织** 按商户类型(在线/离线)
### 测试结构示例
```php
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` 等),并实现所有界面方法(`add``log``debug``info``warning``error``emergency``alert``critical``notice`)。
### 使用模拟日志记录器
```php
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 调用等)