| name | java-testing |
| description | 为 Java / Spring Boot 项目编写符合团队规范的测试——覆盖单元/集成测试、AOP Mock 第三方异常、错误码契约(@ExceptionHandler)与 X-Simulate-Failure 故障注入 Header 协作。可作为 integration-test skill 委托的 testing adapter——按 spec.md §7 生成集成测试 + 跑 mvn verify + 输出 verdict.json。TRIGGER:写/补/改测试,Mock 第三方异常(Token 过期/限流/超时),定义错误码,被 evaluator 委托跑集成测试,编辑 *Test.java。SKIP:性能压测、覆盖率配置、只问 JUnit API 用法。 |
Java 测试规范化指南
本指南不教 JUnit / Mockito / AssertJ / MockMvc / WireMock 的基础 API,只约定团队规范、决策路径、踩过的坑。
双重身份:
- 规范文档(主)——团队成员 / Generator 写测试时参照
- testing adapter(从)——被
/integration-test 委托时,按下方 §"作为 testing adapter 调用" 协议生成+跑+输出 verdict.json
作为 testing adapter 调用(evaluator Phase 2 入口)
当 /integration-test 经 route.py 路由到本 skill 时,执行以下契约:
输入(主 Claude 透传)
| 参数 | 含义 |
|---|
project_root | 被测项目绝对路径 |
spec_path | docs/task/store/<story_id>/spec.md(含 §7 AC↔实现+测试映射) |
story_id | 任务 ID,决定 verdict.json 落地子目录 |
执行步骤
- 读 spec.md §7 → 提取每个 AC 的【实现位置 + 建议集成测试方法名】
- 生成
*IntegrationTest.java —— 按本文档 §测试分层与命名 + §集成测试断言三件套 写,不复用 Generator 的测试(GAN 边界)
- 运行
mvn verify -DskipTests=false -Dit.test=<生成的测试类>(被测项目根目录),失败也要继续解析
- 解析
target/failsafe-reports/TEST-*.xml(Failsafe XML) → 转 verdict.json schema
- 写 verdict.json 到
<project_root>/docs/reports/integration-tests/<story_id>/verdict.json
AC-split 并行模式(流程 AC 较多时加速生成)
Step 2 的"生成"可按 AC fan-out 到多个 task subagent 并行编写,压缩生成墙钟,验收语义不变。
触发:去掉横切 AC 后,单个 fixture family 内流程 AC > 4 → 启用;否则串行单文件。
骨架(详见 ac-split-parallel.md):
- 解析 spec §7,区分流程 AC / 横切 AC
- 两级分组:先按 fixture family(HTTP 契约层 vs 编排层),再在重 family 内按业务阶段分 3~4 组;横切 AC 跟随它贯穿的流程组(不独立 fan-out)
- 串行 prelude:每个重 family 生成抽象支撑类
<Family>ITSupport(共享 mock + 工厂 + 数据 helper + family 桩),先落盘
- 并行 fan-out:一条消息内并发起多个 Agent(
general-purpose,非 generator),每组写 <Group>IntegrationTest extends 支撑类,只依据 spec §7 + 生产源码,返回只给文件路径 + AC 映射
- join:单次
mvn verify -Dit.test='*IntegrationTest' 编译并跑全部文件
- 聚合:解析全部 failsafe XML → 单份 verdict.json(
meta.test_files[] 列全部分组文件)
安全阀:小 story(≤4)走串行;环境不支持嵌套 subagent 自动降级串行(不阻塞 Phase 2);单组不编译/没产文件,该组修复重试 ≤ 2 次,超限标 ERROR。
verdict.json 字段填法(对照 integration-test/SKILL.md schema)
| 字段 | Java 取值方式 |
|---|
verdict | failures+errors == 0 → PASS;基础设施失败(编译/服务起不来) → ERROR;否则 FAIL |
totals.{tests,passed,failed,errors,skipped} | 直接累加各 <testcase> 节点状态 |
ac_coverage.passed_acs / failed_acs | 通过测试方法名 should_*_When_* 反查 spec §7 的 AC 映射 |
failures[].ac / test_method / reason / severity | 失败 <testcase> 的 <failure message="..."> 文本 + 反查 AC;severity=major |
meta.test_framework | "junit5" |
meta.skill_used | "java-testing" |
meta.test_file_path | 生成的 IT 文件绝对路径 |
失败容忍
| 场景 | verdict | meta.error_message |
|---|
mvn 命令不存在 | ERROR | mvn not on PATH |
| 编译失败 | ERROR | compile failed: <maven 输出末 20 行> |
| Failsafe 没产生 XML | ERROR | no failsafe-reports/*.xml found |
| Spring 上下文起不来 | ERROR | Spring context failed: <异常类> |
| 测试跑了但断言 FAIL | FAIL | (不写 error_message,失败详情在 failures[]) |
三条不可妥协的红线
三条不可妥协的红线
- 业务代码绝不写测试逻辑。任何
if (env == "test") / if (header == "mock") 写在 Service / Controller 里都是错的——用 AOP 切面 + @Profile("test") 隔离,详见 aop-mock-pattern.md。
- 异常处理器必须显式设 Content-Type。
@RestControllerAdvice 返回错误体前先 .contentType(MediaType.APPLICATION_JSON)——尤其当接口 produces = "application/pdf" 时(曾发生过 HttpMessageNotWritableException 事故,见 troubleshooting.md)。
- REST 接口的错误响应永远是结构化 JSON。统一格式
{ code, message, timestamp, path, traceId },前端 / QA 按 code 字段断言,永远不依赖 message 文案。GraphQL / gRPC / SSE 不在此规则内。
测试分层与命名
| 类型 | 命名 | 工具 | 阶段 |
|---|
| 单元 | *Test.java | JUnit5 + Mockito + AssertJ | mvn test(Surefire) |
| 集成 | *IntegrationTest.java | @SpringBootTest + MockMvc + WireMock | mvn verify(Failsafe) |
两个必须做的项目配置:
- 测试包路径镜像 main(
src/main/.../UserService.java ↔ src/test/.../UserServiceTest.java)
- pom.xml 给 Failsafe 显式配
<include>**/*IntegrationTest.java</include>,给 Surefire 配同款 <exclude>,避免一份测试跑两遍
命名公式:should_<期望>_When_<场景>(全项目统一一种,不混用 BDD / DisplayName 风格)。
决策树:写哪种测试
要测的对象是?
├─ 单一类纯逻辑(无 IO、无 Spring 依赖) → 单元测试 + Mock 依赖
├─ Controller 请求/响应/校验/序列化/错误码 → 集成测试 + MockMvc
├─ 认证 / 授权 / 角色权限 → 集成测试 + @WithMockUser
├─ Repository / JPA 查询 → 集成测试 + Testcontainers
├─ 调第三方 HTTP(重试 / 降级) → 集成测试 + WireMock
├─ 上游 schema 漂移防护(关键业务接口) → 契约测试(Pact)
├─ QA 在测试环境复现第三方故障 → AOP 切面 Mock
├─ @Async / @Scheduled / Kafka 异步任务 → 集成测试 + Awaitility,不要 @Transactional
└─ 跨 Service 复杂编排 → 先拆 Service 单元测,再 1-2 个集成测试验证编排
FIRST 原则
Fast / Isolated / Repeatable / Self-validating / Timely——任何违反项都是技术债。
关键工程纪律:
- 注入
Clock 而非用 LocalDateTime.now(),否则测试不可重复
- Testcontainers 镜像必须 pin 到 patch 版本(
postgres:15.3,禁止 :15 或 :latest)
- 使用 Singleton Container 模式跨测试共享
团队项目约定(AI 不知道的部分)
错误码字典(项目契约)
| HTTP | code | 说明 |
|---|
| 401 | TOKEN_EXPIRED / TOKEN_INVALID / UNAUTHENTICATED | 认证类 |
| 403 | FORBIDDEN | 已认证但权限不足 |
| 404 | RESOURCE_NOT_FOUND(可细化如 PDF_NOT_FOUND) | 资源不存在 |
| 429 | RATE_LIMITED | 限流 |
| 502 | THIRD_PARTY_ERROR | 上游业务异常 |
| 503 | SERVICE_UNAVAILABLE | 上游不可用(重试耗尽走这个) |
| 500 | INTERNAL_ERROR | 兜底,必须告警 |
QA 故障注入 Header(团队特色)
第三方异常通过 X-Simulate-Failure: <type> 触发,仅在 spring.profiles.active=test 时生效:
| 值 | 触发 | 期望响应 |
|---|
token_expired | TokenExpiredException | 401 / TOKEN_EXPIRED |
timeout | SocketTimeoutException | 调用方超时 / 503 |
server_error | ServiceUnavailableException | 503 / SERVICE_UNAVAILABLE |
rate_limit | RateLimitExceededException | 429 / RATE_LIMITED |
实现方案见 aop-mock-pattern.md,QA 协作 SOP 见 qa-collaboration.md。
集成测试断言三件套(团队约定)
异常响应必须断言这三项,不能只看 status:
status() + header().contentType(APPLICATION_JSON) + jsonPath("$.code").value("...")
Gotchas(团队踩过的具体坑)
- 业务代码绝不写
if (env == "test") —— 用 AOP + @Profile("test") 隔离(详见 aop-mock-pattern.md)
@RestControllerAdvice 错误响应必须显式 .contentType(APPLICATION_JSON)(曾因 PDF 接口踩 HttpMessageNotWritableException)
- Boot 3.4+:
@MockBean → @MockitoBean(容易漏改,编译不报错运行时 NPE)
- Testcontainers 镜像必须 pin patch 版本(
postgres:15.3),禁 :15 或 :latest(否则 CI 不可重复)
- 异步测试不要加
@Transactional(事务边界与 @Async 冲突,异步任务看不到主线程的 DB 数据)
- 断异常响应必须三件套:
status + Content-Type + jsonPath("$.code"),只断 status 漏掉契约破坏
LocalDateTime.now() 让测试不可重复 —— 注入 Clock 并 mock 时间
references 索引
适用版本
Spring Boot 3.2+ · JUnit 5.10+ · Mockito 5.x · WireMock 3.x · AssertJ 3.x
版本陷阱:
- Boot 3.4+:
@MockBean → @MockitoBean
- Boot 3.1+:Testcontainers 用
@ServiceConnection 替代 @DynamicPropertySource
- Boot 3.0+:
javax.* → jakarta.*
- 老项目(Boot 2.x / JUnit 4):先告知用户、再调整示例语法
扩展工具(按需引入)
JaCoCo 覆盖率 · PIT 突变测试 · ArchUnit 架构规则 · Toxiproxy 网络故障注入 · Gatling 性能基线。