testing-knowledge

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Testing 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 logic
Rule: 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}
ExampleMethodScenarioExpected
test_calculate_total_with_discount_returns_reduced_amount
calculateTotalwith discountreturns reduced amount
test_confirm_when_already_shipped_throws_exception
confirmwhen already shippedthrows exception
test_email_with_invalid_format_fails_validation
Email (VO)with invalid formatfails validation
test_{方法名}_{场景}_{预期结果}
示例方法场景预期结果
test_calculate_total_with_discount_returns_reduced_amount
calculateTotal应用折扣返回减免后的金额
test_confirm_when_already_shipped_throws_exception
confirm订单已发货时抛出异常
test_email_with_invalid_format_fails_validation
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

快速质量检查清单

RuleCheck
One test = one behaviorSingle assertion group
Test is documentationName reads as specification
No logic in testsNo if/for/while
Fast execution<100ms per unit test
Mock interfaces onlyNever mock VO, Entity, final
≤3 mocks per testMore = design smell
Behavior over implementationTest WHAT, not HOW
规则检查项
一个测试对应一个行为仅包含一组断言
测试即文档名称可作为规格说明阅读
测试中无业务逻辑不包含if/for/while等逻辑
执行速度快单元测试耗时<100ms
仅模拟接口绝不模拟值对象、实体、final类
每个测试最多3个模拟对象超过3个意味着设计可能存在问题
优先验证行为而非实现测试“做什么”,而非“怎么做”

DDD Component Testing

DDD组件测试

ComponentTest FocusMocks Allowed
Value ObjectValidation, equality, immutabilityNone
EntityState transitions, business rulesNone
AggregateInvariants, consistency, eventsNone
Domain ServiceBusiness logic spanning aggregatesRepository (Fake)
Application ServiceOrchestration, transactionsRepository, EventDispatcher
RepositoryCRUD operationsDatabase (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

测试替身快速参考

TypePurposeWhen to Use
StubReturns canned answersExternal API responses
MockVerifies interactionsEvent publishing
FakeWorking implementationInMemory repository
SpyRecords callsLogging, 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
    └── 否 → 使用Stub

Common Test Smells

常见测试坏味道

SmellDetectionFix
Logic in Test
if
,
for
,
while
in test
Extract to helper or parameterize
Mock Overuse>3 mocksRefactor design, use Fakes
Mystery GuestExternal files, hidden dataInline test data or use Builder
Eager TestTests multiple behaviorsSplit into separate tests
Fragile TestBreaks on refactorTest behavior, not implementation
坏味道识别方式修复方案
测试中包含业务逻辑测试代码里有
if
for
while
提取到辅助方法或参数化测试
过度使用模拟对象模拟对象数量>3重构设计,改用Fake
神秘访客依赖外部文件、隐藏数据内联测试数据或使用Builder模式
过度测试一个测试验证多个行为拆分为多个独立测试
脆弱测试重构后容易失效验证行为而非实现细节

References

参考资料

For detailed information, load these reference files:
  • references/unit-testing.md
    — Unit test patterns and examples
  • references/integration-testing.md
    — Integration test setup and patterns
  • references/ddd-testing.md
    — Testing DDD components (VO, Entity, Aggregate, Service)
如需详细信息,请查看以下参考文档:
  • references/unit-testing.md
    — 单元测试模式与示例
  • references/integration-testing.md
    — 集成测试配置与模式
  • references/ddd-testing.md
    — DDD组件测试(值对象、实体、聚合根、服务)