1. 为什么 Java 团队需要把单元测试交给 Claude Skills单元测试这件事写起来不难写好很难。我见过太多 Java 项目业务代码写得漂漂亮亮一翻src/test目录要么是空的要么是几年前为了应付覆盖率检查随手糊上去的assertNotNull。真正让人头疼的不是不会写测试而是每次都要重复写同样的东西Mock 声明、BeforeEach初始化、Given-When-Then 三段式、verify调用校验……一个 Service 类五六个方法测试代码轻松三四百行风格还因人而异。Claude Skills 解决的正是这个痛点。它本质上是给 Claude 装了一本团队测试规范手册把命名规则、Mock 配置、断言习惯、覆盖率目标全部固化成一个可复用的技能包。当你在 Claude Code 里说帮我给 UserService 写单元测试它会自动加载这个 Skill按你定义的规范生成代码而不是每次靠你临时描述需求。这篇文章面向的是需要批量补齐测试覆盖的 Java 研发团队。我会给出可直接复制的 Skill 配置骨架、settings.json示例然后拿一个典型的 Service 类走完整流程生成测试代码、编译、运行、看覆盖率报告最后判断生成质量到底达不达标。如果你正在为测试覆盖率卡在 40% 上不去发愁这套路径值得试一遍。2. 前置准备TaoToken 接入与 Claude Code 环境在动手写 Skill 之前得先把 Claude Code 跑起来。这里我用 TaoToken 作为模型接入层它提供统一的 API 入口配置简单适合团队统一管理密钥和用量。2.1 获取 API Key登录 TaoToken 控制台在 API Keys 页面创建一个新密钥。建议按项目或按人分配方便后续排查用量。创建后复制密钥格式类似sk-xxxxxxxx只显示一次记得存好。如果你还没注册可以从官网入口进入https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content 注册后在控制台左侧菜单找到 API Keys 即可。2.2 配置 Claude Code 的 settings.jsonClaude Code 的配置文件通常位于用户目录下的.claude/settings.json。把 TaoToken 的 API 地址和密钥写进去{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_AUTH_TOKEN: sk-你的密钥, ANTHROPIC_MODEL: claude-sonnet-4-5-20250929 }, permissions: { allow: [ Read, Write, Bash(mvn *), Bash(gradle *) ] } }这里有两个关键点。第一ANTHROPIC_BASE_URL填https://taotoken.net/api注意不要带任何查询参数保持干净。第二permissions.allow里放行Read、Write和 Maven/Gradle 命令这样 Claude 才能读取你的 Java 源文件、写入测试文件、并执行编译测试命令。如果不放行 Bash后面验证环节会卡住。注意ANTHROPIC_AUTH_TOKEN是敏感信息不要把settings.json提交到 Git 仓库。建议在.gitignore里加上.claude/settings.local.json把密钥放在本地文件团队共享的settings.json只保留非敏感配置。2.3 验证接入是否成功配置完成后在终端执行claude --version然后进入一个 Java 项目目录启动 Claude Codecd your-java-project claude在交互界面里输入一句你好请用一句话介绍你自己如果能正常返回说明接入成功。如果报 401 或连接超时回到第 5 节排查。3. 可复制的 Skill 配置骨架Skill 的核心是一个SKILL.md文件放在项目的.claude/skills/java-unit-test/目录下。下面这份骨架可以直接复制再按团队习惯微调。3.1 目录结构your-java-project/ ├── .claude/ │ ├── settings.json │ └── skills/ │ └── java-unit-test/ │ └── SKILL.md ├── src/ │ ├── main/java/... │ └── test/java/... └── pom.xml创建目录的命令mkdir -p .claude/skills/java-unit-test3.2 SKILL.md 完整骨架--- name: java-unit-test description: 为 Java 项目生成 JUnit 5 Mockito 单元测试。当用户要求编写单元测试、生成测试用例、提升测试覆盖率时使用。 allowed-tools: Read, Write, Bash version: 1.0.0 --- ## 触发条件 当用户提出以下需求时激活 - 帮我写单元测试 - 为这个类生成测试 - 提升测试覆盖率 - junit test / mockito test ## 前置检查清单 生成测试前必须完成 1. 读取被测 Java 源文件识别所有 public 方法 2. 记录方法签名参数类型、返回值、声明的异常 3. 识别依赖字段Autowired / 构造注入确定需要 Mock 的对象 4. 确认项目使用 JUnit 5 和 Mockito 3.x 以上版本 ## 测试代码生成规范 ### 命名规范 - 测试类[被测类名]Test.java - 测试方法方法名_场景_预期结果 ### 结构模板 - 类级别用 ExtendWith(MockitoExtension.class) - Mock 对象用 Mock 声明被测类用 InjectMocks - 每个测试方法遵循 Given-When-Then 三段式 - 使用 DisplayName 描述场景 ### Mock 配置 - 返回值when(mock.method(any())).thenReturn(value) - 抛异常when(mock.method(any())).thenThrow(new XxxException()) - 验证调用verify(mock).method(eq(specific)) - 验证未调用verify(mock, never()).method(any()) ## 测试用例设计原则 每个 public 方法至少覆盖 - 正常场景有效输入 - 边界场景空集合、单元素、临界值 - 异常场景预期抛出的异常 - 空值场景null 参数 覆盖率目标行覆盖 ≥ 80%分支覆盖 ≥ 70%。 ## 常见问题处理 - 静态方法使用 mockStatic - final 类配置 mock-maker-inline - 私有方法通过 public 方法间接覆盖不直接测试这份骨架的关键在于前置检查清单和结构模板两段。前者保证 Claude 不会在没读懂源码的情况下瞎编测试后者保证生成的代码风格统一。你可以把团队特有的规范比如必须用 AssertJ 而不是 JUnit 原生断言加进去。3.3 让 Skill 生效保存SKILL.md后Claude Code 从 v2.1.1 起支持热重载无需重启。在交互界面输入/java-unit-test可以手动触发或者直接说帮我给 UserService 写单元测试让它自动命中。4. 实战给 UserService 生成测试并验证光看配置没感觉直接上一个真实场景。假设项目里有个UserService代码如下Service public class UserService { Autowired private UserRepository userRepository; Autowired private EmailService emailService; Autowired private PasswordEncoder passwordEncoder; public Long createUser(UserDTO dto) { if (dto null) { throw new IllegalArgumentException(用户信息不能为空); } if (userRepository.existsByEmail(dto.getEmail())) { throw new UserAlreadyExistsException(邮箱已存在); } User user new User(); user.setEmail(dto.getEmail()); user.setPassword(passwordEncoder.encode(dto.getPassword())); User saved userRepository.save(user); emailService.sendWelcomeEmail(user.getEmail()); return saved.getId(); } public User getUserById(Long id) { if (id null) { throw new IllegalArgumentException(用户 ID 不能为空); } return userRepository.findById(id) .orElseThrow(() - new UserNotFoundException(用户不存在)); } }4.1 触发 Skill 生成测试在 Claude Code 里输入帮我为 src/main/java/com/example/service/UserService.java 写单元测试Claude 会先读取源文件识别出三个依赖UserRepository、EmailService、PasswordEncoder和两个 public 方法然后按 Skill 规范生成UserServiceTest.java。生成结果大致如下ExtendWith(MockitoExtension.class) DisplayName(UserService 单元测试) class UserServiceTest { Mock private UserRepository userRepository; Mock private EmailService emailService; Mock private PasswordEncoder passwordEncoder; InjectMocks private UserService userService; Test DisplayName(创建用户 - 成功场景) void createUser_Success() { UserDTO dto new UserDTO(testexample.com, password123); User saved new User(1L, testexample.com, encoded); when(userRepository.existsByEmail(anyString())).thenReturn(false); when(passwordEncoder.encode(anyString())).thenReturn(encoded); when(userRepository.save(any(User.class))).thenReturn(saved); Long userId userService.createUser(dto); assertNotNull(userId); assertEquals(1L, userId); verify(userRepository).existsByEmail(testexample.com); verify(emailService).sendWelcomeEmail(testexample.com); } Test DisplayName(创建用户 - 邮箱已存在抛异常) void createUser_EmailExists_ThrowsException() { UserDTO dto new UserDTO(existingexample.com, password123); when(userRepository.existsByEmail(anyString())).thenReturn(true); assertThrows(UserAlreadyExistsException.class, () - userService.createUser(dto)); verify(userRepository, never()).save(any(User.class)); } Test DisplayName(创建用户 - 参数为空抛异常) void createUser_NullParam_ThrowsException() { assertThrows(IllegalArgumentException.class, () - userService.createUser(null)); } Test DisplayName(根据 ID 获取用户 - 成功场景) void getUserById_Success() { User user new User(1L, testexample.com, encoded); when(userRepository.findById(1L)).thenReturn(Optional.of(user)); User result userService.getUserById(1L); assertEquals(testexample.com, result.getEmail()); } Test DisplayName(根据 ID 获取用户 - 用户不存在抛异常) void getUserById_NotFound_ThrowsException() { when(userRepository.findById(999L)).thenReturn(Optional.empty()); assertThrows(UserNotFoundException.class, () - userService.getUserById(999L)); } }4.2 编译与运行生成后立刻验证别等提交了才发现编译不过。执行mvn test -DtestUserServiceTest如果项目用 Gradle./gradlew test --tests UserServiceTest预期输出类似[INFO] Tests run: 5, Failures: 0, Errors: 0, Skipped: 0 [INFO] BUILD SUCCESS5 个测试全部通过说明生成的代码在语法和逻辑上都是可用的。4.3 覆盖率验证光跑通不够还得看覆盖率。用 JaCoCo 生成报告mvn test jacoco:report报告在target/site/jacoco/index.html打开后找到UserService看行覆盖和分支覆盖。按 Skill 里定义的目标行覆盖应该 ≥ 80%。如果createUser的分支覆盖偏低通常是漏了某个异常分支回到 Claude Code 里说补充 createUser 的边界测试它会按 Skill 规范继续生成。5. 本篇常见错误排查生成测试的过程中有几个坑几乎每个团队都会踩一遍。5.1 编译报错找不到 MockitoExtension报错信息cannot find symbol: class MockitoExtension原因是pom.xml里 Mockito 版本太低或者没引入mockito-junit-jupiter。检查依赖dependency groupIdorg.mockito/groupId artifactIdmockito-junit-jupiter/artifactId version5.5.0/version scopetest/scope /dependencyJUnit 5 需要mockito-junit-jupiter不是老的mockito-core单独能搞定的。5.2 静态方法 Mock 失败报错The used MockMaker SubclassByteBuddyMockMaker does not support the creation of static mocks这是 Mockito 默认 mock maker 不支持静态方法。在src/test/resources/mockito-extensions/下创建文件org.mockito.plugins.MockMaker内容写一行mock-maker-inline然后测试里用try (MockedStaticXxxUtils mocked mockStatic(XxxUtils.class))包裹。5.3 接入层报 401 或超时如果 Claude Code 启动后请求报 401先检查settings.json里的ANTHROPIC_AUTH_TOKEN是否复制完整有没有多余空格。如果报连接超时确认ANTHROPIC_BASE_URL是https://taotoken.net/api不要手动加/v1之类的后缀。密钥管理可以在控制台的 API Keys 页面重新生成https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentjava_unit_test_skillutm_campaignrewrite 。5.4 Skill 没被触发说了写单元测试但 Claude 没按 Skill 规范生成通常是description字段没写清楚触发关键词。回到SKILL.md确保description里包含单元测试测试用例覆盖率这些词。也可以手动输入/java-unit-test强制触发。5.5 生成的测试过度 Mock有些生成结果会把PasswordEncoder这种纯函数也 Mock 掉导致测试失去意义。在 Skill 的Mock 配置段落里加一条规则对于无副作用的工具类如加密、格式化优先使用真实实现不 Mock。这样 Claude 会判断哪些依赖该 Mock、哪些该用真的。6. 团队落地与后续动作单个项目跑通之后把.claude/skills/java-unit-test/提交到 Git团队成员拉取后自动生效测试风格立刻统一。后续可以根据反馈迭代 Skill比如加入 AssertJ 断言规范、参数化测试模板、或者针对 Controller 层的 MockMvc 配置。如果你想把生成能力用在更长期的编码任务上比如批量给几十个 Service 补测试可以考虑 Coding Plan 这类按周期计费的方案成本比按次调用更可控https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentjava_unit_test_skillutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentjava_unit_test_skillutm_campaignrewrite 里面有完整的参数说明和示例。最后提醒一句Skill 生成的是符合规范的初稿不是可以直接合并的终稿。跑通编译、看覆盖率、人工 review 断言逻辑这三步一个都不能省。把 Skill 当成一个熟悉团队规范的实习生它写得快但最终把关的还是你。