| name | tdd-guide |
| description | Test-driven development guide for writing JUnit 5 tests, analyzing coverage gaps, and guiding red-green-refactor workflows. Use when the user asks to write tests, improve test coverage, practice TDD, generate test stubs, or mentions JUnit/testing. |
TDD Guide
Test-driven development skill for generating tests, analyzing coverage, and guiding red-green-refactor workflows. Adapted from alirezarezvani/claude-skills for JUnit 5 and the jhelm project.
Workflows
Generate Tests from Code
- Provide source code (Java class or method)
- Target framework: JUnit 5 (
org.junit.jupiter.api)
- Follow jhelm conventions:
- Use
Assertions (not AssertJ)
- Use
@TempDir for temporary files
- Use
@ParameterizedTest with @CsvSource or @MethodSource to avoid duplication
- Prefer real test data over Mockito mocks
- Mockito acceptable only for HTTP (
CloseableHttpClient) and Kubernetes (KubeService)
- Validation: Tests compile and cover happy path, error cases, edge cases
Analyze Coverage Gaps
- Run JaCoCo:
./mvnw test (generates target/site/jacoco/jacoco.xml)
- Use
/jacoco skill to analyze coverage by module
- Prioritize gaps:
- P0: 0% covered classes with many lines (high impact)
- P1: Partially covered classes with uncovered branches
- P2: Low-risk utility classes
- Generate tests for P0 items first
- Target: 80% minimum coverage per module
TDD New Feature
- Write failing test first (RED)
- Run:
./mvnw test -Dtest=NewFeatureTest -pl <module>
- Implement minimal code to pass (GREEN)
- Run test again to confirm pass
- Refactor while keeping tests green (REFACTOR)
- Run
./mvnw validate to verify formatting + PMD + checkstyle
JUnit 5 Patterns for jhelm
Basic Test
@Test
void testChartLoadingFromDirectory() throws Exception {
Chart chart = ChartLoader.load(Path.of("src/test/resources/test-charts/minimal"));
assertEquals("minimal", chart.getMetadata().getName());
assertNotNull(chart.getValues());
}
Parameterized Test (avoid duplication)
@ParameterizedTest
@CsvSource(delimiter = '|', value = {
"upper | hello | HELLO",
"lower | HELLO | hello",
"title | hello world | Hello World"
})
void testStringFunctions(String func, String input, String expected)
throws IOException, TemplateException {
assertEquals(expected, exec("{{ " + func + " \"" + input + "\" }}"));
}
Template Engine Test
@Test
void testTemplateRendering() throws IOException, TemplateException {
GoTemplate template = new GoTemplate();
template.parse("test", "Hello {{ .name }}!");
StringWriter writer = new StringWriter();
template.execute("test", Map.of("name", "World"), writer);
assertEquals("Hello World!", writer.toString());
}
Mocking External Dependencies
@Test
void testPullChartWithAuth() throws Exception {
CloseableHttpClient mockClient = mock(CloseableHttpClient.class);
when(mockClient.execute(any())).thenReturn(httpAnswer(200, chartBytes));
RepoManager rm = new RepoManager();
rm.setHttpClient(mockClient);
rm.pull("myrepo", "mychart", "1.0.0", tempDir);
assertTrue(tempDir.resolve("mychart-1.0.0.tgz").toFile().exists());
}
Using @TempDir
@Test
void testChartPackaging(@TempDir Path tempDir) throws Exception {
Path chartDir = tempDir.resolve("mychart");
Files.createDirectories(chartDir);
Files.writeString(chartDir.resolve("Chart.yaml"), """
apiVersion: v2
name: mychart
version: 1.0.0
""");
File tgz = ChartPackager.pack(chartDir.toFile(), tempDir.toFile());
assertTrue(tgz.exists());
}
Red-Green-Refactor Cycle
RED — Write Failing Test
@Test
void testNewFeature() {
MyService service = new MyService();
String result = service.newMethod("input");
assertEquals("expected output", result);
}
Run: ./mvnw test -Dtest=MyServiceTest#testNewFeature -pl jhelm-core
Expected: FAIL (method doesn't exist yet)
GREEN — Minimal Implementation
public String newMethod(String input) {
return "expected output";
}
Run: ./mvnw test -Dtest=MyServiceTest#testNewFeature -pl jhelm-core
Expected: PASS
REFACTOR — Clean Up
- Extract constants, reduce duplication
- Run full test suite:
./mvnw test -pl jhelm-core
- Run validation:
./mvnw validate -pl jhelm-core
Coverage Analysis Strategy
When increasing coverage for a module:
- Start with modules closest to threshold (small gap = quick win)
- Focus on 0% coverage classes with many lines (high impact)
- Use the
/jacoco skill to identify specific uncovered lines
- Incremental approach: 60% → 70% → 80%
- Check what's already tested before writing duplicate tests
jhelm Module Coverage Targets
| Module | Target | Notes |
|---|
| jhelm-core | 80% | Action classes, Engine, RepoManager |
| jhelm-cli | 80% | CLI commands via Picocli CommandLine.execute() |
| jhelm-gotemplate | 80% | Template functions, Lexer, Parser |
| jhelm-kube | 80% | Mock KubeService for k8s operations |
Mutation Testing (Advanced)
For critical paths, use PIT (PiTest) to verify test quality beyond coverage:
./mvnw org.pitest:pitest-maven:mutationCoverage -pl jhelm-core
- 100% line coverage != good tests — coverage tells you code was executed, not verified
- Target 85%+ mutation score on critical modules (auth, chart extraction, values merging)
Bounded Autonomy Rules
Stop and Ask When
- Ambiguous requirements or unclear acceptance criteria
- Test count exceeds 50 for a single class
- Security-sensitive logic (auth, crypto, chart extraction)
- External dependencies with undocumented behavior
Continue Autonomously When
- Clear spec with well-defined inputs/outputs
- CRUD operations with well-defined models
- Pure functions (deterministic input/output)
- Existing test patterns to follow in the codebase