测试实现(Testing Implementation)
适用场景
- 单元测试实现(业务逻辑、工具函数)
- 集成测试实现(DB、Redis、HTTP)
- 契约测试(消费者驱动 / Pact)
- E2E 测试(少量、关键路径)
- Mock / Stub / Fake 设计
- 测试数据管理
- 性能基准测试
核心原则
- 测试金字塔:单元(70%)+ 集成(20%)+ E2E(10%)
- AAA 模式:Arrange(准备)+ Act(执行)+ Assert(断言)
- 一个用例测一件事;多个断言但围绕一个行为
- 命名说明意图,如 shouldReturnError_WhenAmountIsNegative,而不是 test_001 / testCreateOrder
- 不测实现细节,测行为输出,不测内部调用顺序
- Mock 适度:Mock 外部依赖,不 Mock 自己写的代码
- 集成测试用真实依赖,用 Testcontainers,不用 H2 / Mock DB
- 测试运行快:单元 < 100ms,集成 < 5s,全套 < 5min
测试金字塔实现
单元测试数量 1000+,覆盖业务逻辑、工具函数;集成测试 50~200 个,覆盖 API 端到端、DB / Redis;E2E 测试 5~15 个,覆盖核心用户旅程,慢且贵。
AAA 模式示例(Java + JUnit 5 + AssertJ)中通过 OrderFactory 准备数据,调用 cancel() 后断言状态变为 CANCELLED 且 cancelledAt 非空;对非法状态则断言抛出 IllegalStateException 并包含提示信息。
单元测试范式
- Java + JUnit 5 + Mockito + AssertJ:使用 @ExtendWith(MockitoExtension.class)、@Mock、@InjectMocks,配合 given/willReturn、verify 进行验证,并用 @ParameterizedTest + @ValueSource 覆盖边界值。
- TypeScript + Jest:使用 jest.Mocked、createMock、beforeEach 构建被测服务,使用 it.each 表驱动覆盖错误码。
- TypeScript + Vitest:使用 describe / it / expect / vi,示例验证已支付订单取消后状态为 CANCELLED。
- Python + pytest:使用 @pytest.fixture、Mock(spec=...)、断言 assert_called_once,并用 @pytest.mark.parametrize 覆盖数量校验。
- Go testing:使用表驱动测试(tests 切片 + t.Run),配合 mockery 生成的 mocks.NewOrderRepository(t)。
集成测试(Testcontainers)
- Spring Boot + Testcontainers:通过 @Testcontainers、@Container 启动 PostgreSQL 16 与 Redis 7,使用 @DynamicPropertySource 注入连接信息,通过 MockMvc 调用接口并断言数据库计数。
- NestJS + Testcontainers:通过 @testcontainers/postgresql 的 PostgreSqlContainer 启动数据库,设置 DATABASE_URL,构建 Nest 应用后使用 supertest 请求接口。
- Python + pytest + testcontainers:使用 PostgresContainer 会话级 fixture,注入 DATABASE_URL 后用 client 请求接口并断言返回。
契约测试(Pact)
消费者驱动契约测试流程:前端(消费者)写期望契约 → Pact Broker 存契约 → 后端(服务方)跑契约测试验证能满足 → 双方都通过即联调安全。可避免接口部署后才发现契约不一致、Mock 与真实服务行为不一致的问题。消费者侧示例使用 PactV3 定义 given / uponReceiving / withRequest / willRespondWith,服务方侧使用 @Provider、@PactBroker、@State 与 PactVerificationInvocationContextProvider 验证交互。
测试替身(Test Doubles)
- Mock:验证调用,如 verify(repo).save(any())
- Stub:返回预设值,如 when(repo.find(1)).thenReturn(user)
- Spy:真实对象 + 监视,如 Spy<EmailService>
- Fake:简化实现,如 InMemoryOrderRepository
- Dummy:占位(不被用),如 null 替代
测试数据工厂
使用 Fishery / Faker 定义工厂,如 userFactory = Factory.define<User>(...),可 build()、带覆盖字段 build({ role: 'admin' })、buildList(10)。
性能基准测试
- Java:JMH,@Benchmark + @OutputTimeUnit
- JS/TS:Vitest bench
- Go:BenchmarkCreateOrder,使用 b.ResetTimer() 与 b.N 循环
测试覆盖率
参考目标:业务逻辑(Service / Domain)≥ 80%,Controller ≥ 60%,Repository ≥ 50%,总体 ≥ 70%。工具包括 Java 的 JaCoCo、JS/TS 的 Jest --coverage / Vitest --coverage、Python 的 pytest-cov、Go 的 go test -cover。注意覆盖率高不等于测试好,覆盖率只是基础线。
工作流程
- TDD(推荐):先写测试(红)→ 写代码让测试过(绿)→ 重构(保绿)
- 单元测试:测纯业务逻辑,Mock 外部依赖,快、可重复
- 集成测试:测端到端流程,用 Testcontainers,主路径 + 关键失败路径
- 契约测试:跨服务调用,消费者驱动
- E2E 测试:关键用户旅程(5~15 条),慢但全面
- 性能基准:关键热路径,监控退化
配套模板
templates/test-implementation-checklist.md — 测试金字塔 + AAA + 测试替身 + Testcontainers + 覆盖率
质量自检
- 单元测试覆盖业务逻辑 ≥ 80%
- 集成测试覆盖主路径
- AAA 模式
- 测试命名表达意图
- 一个用例测一件事
- 不测实现细节
- Mock 适度(不 Mock 自己代码)
- 集成测试用真依赖(Testcontainers)
- 测试快(单元 < 100ms)
- 测试独立(不依赖顺序)
- 测试数据用工厂
- 关键路径有 E2E
- 跨服务有契约测试
- 性能基准(热路径)
常见坑
- 测实现细节——重构时大量测试挂
- 过度 Mock——Mock 自己代码 = 测假
- 用 H2 / SQLite 替代 PG——行为差异
- 测试数据手写——下次跑环境变了
- 测试顺序依赖——并行就崩
- 覆盖率追求 100%——getter/setter 也测
- 断言不精确——.toBeTruthy() 而非具体值
- 不写失败路径——只测 happy path
- 测试不快——5 分钟跑完就没人跑
- 测试不独立——共享状态污染
- Mock 与真实行为不一致——线上 Bug
- 没有契约测试——前后端联调返工
- 重构没有测试网——不敢改
- 测试代码不维护——"测试代码不重要"
与其他 skill 的协作
上游:domain-modeling → 单元测试领域模型;api-implementation → 集成测试 Controller;data-access → 仓储测试。 下游:qa-engineer 工作流 → QA 用例 + 测试策略;automation-qa 工作流 → CI 集成;observability → 测试时的日志。
相关参考
- 项目根 references/backend-tech-stack-guide.md — 测试工具
- qa-engineer 工作流测试方法论