testing-knowledge
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTesting Knowledge Base
测试知识库
Quick reference for PHP testing patterns, principles, and best practices.
PHP测试模式、原则与最佳实践快速参考。
Testing Pyramid
测试金字塔
/\
/ \ Functional (10%)
/────\ - E2E, browser tests
/ \ - Slow, fragile
/────────\ Integration (20%)
/ \ - DB, HTTP, queues
/────────────\Unit (70%)
/ \- Fast, isolated
/________________\- Business logicRule: 70% unit, 20% integration, 10% functional. Invert the pyramid = slow, brittle test suite.
/\
/ \ 功能测试 (10%)
/────\ - 端到端(E2E)、浏览器测试
/ \ - 速度慢、易失效
/────────\ 集成测试 (20%)
/ \ - 数据库、HTTP、队列相关
/────────────\单元测试 (70%)
/ \- 速度快、隔离性强
/________________\- 业务逻辑验证规则: 70%单元测试、20%集成测试、10%功能测试。倒置金字塔会导致测试套件运行缓慢且脆弱。
AAA Pattern (Arrange-Act-Assert)
AAA Pattern(准备-执行-断言)
php
public function test_order_calculates_total_with_discount(): void
{
// Arrange — set up test data
$order = new Order(OrderId::generate());
$order->addItem(new Product('Book', Money::EUR(100)));
$discount = new PercentageDiscount(10);
// Act — execute the behavior
$total = $order->calculateTotal($discount);
// Assert — verify the outcome
self::assertEquals(Money::EUR(90), $total);
}Rules:
- One blank line between sections
- Single Act per test
- Assert behavior, not implementation
php
public function test_order_calculates_total_with_discount(): void
{
// Arrange — 设置测试数据
$order = new Order(OrderId::generate());
$order->addItem(new Product('Book', Money::EUR(100)));
$discount = new PercentageDiscount(10);
// Act — 执行目标行为
$total = $order->calculateTotal($discount);
// Assert — 验证结果
self::assertEquals(Money::EUR(90), $total);
}规则:
- 各部分之间空一行
- 每个测试仅包含一个执行步骤(Act)
- 断言验证行为,而非实现细节
Naming Conventions
命名规范
PHPUnit Style
PHPUnit 风格
test_{method}_{scenario}_{expected}| Example | Method | Scenario | Expected |
|---|---|---|---|
| calculateTotal | with discount | returns reduced amount |
| confirm | when already shipped | throws exception |
| Email (VO) | with invalid format | fails validation |
test_{方法名}_{场景}_{预期结果}| 示例 | 方法 | 场景 | 预期结果 |
|---|---|---|---|
| calculateTotal | 应用折扣 | 返回减免后的金额 |
| confirm | 订单已发货时 | 抛出异常 |
| Email(值对象) | 格式无效时 | 验证失败 |
Pest Style
Pest 风格
php
it('calculates total with discount applied')
it('throws exception when confirming shipped order')
it('fails validation for invalid email format')php
it('calculates total with discount applied')
it('throws exception when confirming shipped order')
it('fails validation for invalid email format')Test Isolation Principles
测试隔离原则
DO
应该做
- Fresh fixtures per test
- Independent test execution (any order)
- Teardown cleans all state
- Use in-memory implementations
- 每个测试使用全新的测试数据
- 测试可独立执行(任意顺序)
- 测试完成后清理所有状态
- 使用内存实现替代真实服务
DON'T
不应该做
- Shared mutable state between tests
- Tests depending on execution order
- Global variables or singletons
- Real external services in unit tests
- 测试之间共享可变状态
- 测试依赖执行顺序
- 使用全局变量或单例
- 单元测试中调用真实外部服务
Quick Quality Checklist
快速质量检查清单
| Rule | Check |
|---|---|
| One test = one behavior | Single assertion group |
| Test is documentation | Name reads as specification |
| No logic in tests | No if/for/while |
| Fast execution | <100ms per unit test |
| Mock interfaces only | Never mock VO, Entity, final |
| ≤3 mocks per test | More = design smell |
| Behavior over implementation | Test WHAT, not HOW |
| 规则 | 检查项 |
|---|---|
| 一个测试对应一个行为 | 仅包含一组断言 |
| 测试即文档 | 名称可作为规格说明阅读 |
| 测试中无业务逻辑 | 不包含if/for/while等逻辑 |
| 执行速度快 | 单元测试耗时<100ms |
| 仅模拟接口 | 绝不模拟值对象、实体、final类 |
| 每个测试最多3个模拟对象 | 超过3个意味着设计可能存在问题 |
| 优先验证行为而非实现 | 测试“做什么”,而非“怎么做” |
DDD Component Testing
DDD组件测试
| Component | Test Focus | Mocks Allowed |
|---|---|---|
| Value Object | Validation, equality, immutability | None |
| Entity | State transitions, business rules | None |
| Aggregate | Invariants, consistency, events | None |
| Domain Service | Business logic spanning aggregates | Repository (Fake) |
| Application Service | Orchestration, transactions | Repository, EventDispatcher |
| Repository | CRUD operations | Database (SQLite) |
| 组件 | 测试重点 | 允许使用的模拟对象 |
|---|---|---|
| 值对象(Value Object) | 验证、相等性、不可变性 | 无 |
| 实体(Entity) | 状态转换、业务规则 | 无 |
| 聚合根(Aggregate) | 不变量、一致性、事件 | 无 |
| 领域服务(Domain Service) | 跨聚合根的业务逻辑 | 仓库(Fake实现) |
| 应用服务(Application Service) | 编排、事务 | 仓库、事件分发器 |
| 仓库(Repository) | CRUD操作 | 数据库(SQLite) |
PHP 8.4 Test Patterns
PHP 8.4测试模式
Unit Test Template
单元测试模板
php
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('unit')]
#[CoversClass(Email::class)]
final class EmailTest extends TestCase
{
public function test_creates_valid_email(): void
{
$email = new Email('user@example.com');
self::assertSame('user@example.com', $email->value);
}
public function test_throws_for_invalid_format(): void
{
$this->expectException(InvalidArgumentException::class);
new Email('invalid');
}
}php
<?php
declare(strict_types=1);
namespace Tests\Unit\Domain;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\Group;
use PHPUnit\Framework\TestCase;
#[Group('unit')]
#[CoversClass(Email::class)]
final class EmailTest extends TestCase
{
public function test_creates_valid_email(): void
{
$email = new Email('user@example.com');
self::assertSame('user@example.com', $email->value);
}
public function test_throws_for_invalid_format(): void
{
$this->expectException(InvalidArgumentException::class);
new Email('invalid');
}
}Integration Test Template
集成测试模板
php
<?php
declare(strict_types=1);
namespace Tests\Integration\Infrastructure;
use PHPUnit\Framework\Attributes\Group;
use Tests\DatabaseTestCase;
#[Group('integration')]
final class DoctrineOrderRepositoryTest extends DatabaseTestCase
{
private OrderRepositoryInterface $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = $this->getContainer()->get(OrderRepositoryInterface::class);
}
public function test_saves_and_retrieves_order(): void
{
// Arrange
$order = OrderMother::pending();
// Act
$this->repository->save($order);
$found = $this->repository->findById($order->id());
// Assert
self::assertNotNull($found);
self::assertTrue($order->id()->equals($found->id()));
}
}php
<?php
declare(strict_types=1);
namespace Tests\Integration\Infrastructure;
use PHPUnit\Framework\Attributes\Group;
use Tests\DatabaseTestCase;
#[Group('integration')]
final class DoctrineOrderRepositoryTest extends DatabaseTestCase
{
private OrderRepositoryInterface $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = $this->getContainer()->get(OrderRepositoryInterface::class);
}
public function test_saves_and_retrieves_order(): void
{
// Arrange
$order = OrderMother::pending();
// Act
$this->repository->save($order);
$found = $this->repository->findById($order->id());
// Assert
self::assertNotNull($found);
self::assertTrue($order->id()->equals($found->id()));
}
}Test Doubles Quick Reference
测试替身快速参考
| Type | Purpose | When to Use |
|---|---|---|
| Stub | Returns canned answers | External API responses |
| Mock | Verifies interactions | Event publishing |
| Fake | Working implementation | InMemory repository |
| Spy | Records calls | Logging, notifications |
| 类型 | 用途 | 使用场景 |
|---|---|---|
| Stub(桩) | 返回预设结果 | 模拟外部API响应 |
| Mock(模拟对象) | 验证交互行为 | 事件发布验证 |
| Fake(伪实现) | 可用的简化实现 | 内存仓库 |
| Spy(间谍) | 记录调用情况 | 日志、通知验证 |
Decision Matrix
决策矩阵
Need to verify a call was made?
├── Yes → Mock or Spy
└── No → Need real behavior?
├── Yes → Fake
└── No → Stub需要验证是否调用了某个方法?
├── 是 → 使用Mock或Spy
└── 否 → 是否需要真实行为?
├── 是 → 使用Fake
└── 否 → 使用StubCommon Test Smells
常见测试坏味道
| Smell | Detection | Fix |
|---|---|---|
| Logic in Test | | Extract to helper or parameterize |
| Mock Overuse | >3 mocks | Refactor design, use Fakes |
| Mystery Guest | External files, hidden data | Inline test data or use Builder |
| Eager Test | Tests multiple behaviors | Split into separate tests |
| Fragile Test | Breaks on refactor | Test behavior, not implementation |
| 坏味道 | 识别方式 | 修复方案 |
|---|---|---|
| 测试中包含业务逻辑 | 测试代码里有 | 提取到辅助方法或参数化测试 |
| 过度使用模拟对象 | 模拟对象数量>3 | 重构设计,改用Fake |
| 神秘访客 | 依赖外部文件、隐藏数据 | 内联测试数据或使用Builder模式 |
| 过度测试 | 一个测试验证多个行为 | 拆分为多个独立测试 |
| 脆弱测试 | 重构后容易失效 | 验证行为而非实现细节 |
References
参考资料
For detailed information, load these reference files:
- — Unit test patterns and examples
references/unit-testing.md - — Integration test setup and patterns
references/integration-testing.md - — Testing DDD components (VO, Entity, Aggregate, Service)
references/ddd-testing.md
如需详细信息,请查看以下参考文档:
- — 单元测试模式与示例
references/unit-testing.md - — 集成测试配置与模式
references/integration-testing.md - — DDD组件测试(值对象、实体、聚合根、服务)
references/ddd-testing.md