Test Skill
Quick Ref: Generate tests, analyze coverage, fill gaps, run TDD loops. Output: passing tests + coverage report in .agents/test/.
YOU MUST EXECUTE THIS WORKFLOW. Do not just describe it.
Generate real tests, run them, verify they pass, and produce coverage artifacts. Do not output a plan and stop.
Modes
| Mode | Trigger | What It Does | |------|---------|--------------| | generate | "generate tests", "write tests", "add tests" | Create tests for existing code | | coverage | "test coverage", "coverage gaps", "missing tests" | Analyze coverage and fill gaps | | strategy | "test strategy", "test architecture" | Recommend test structure and patterns | | tdd | "tdd", "red green refactor" | Red-green-refactor loop for new features |
Default mode is generate when unspecified. Detect from user intent.
Step 0: Detect Language and Load Standards
Scan the project root for language markers. Stop at the first match:
| Marker File | Language | Test Framework | Coverage Command | |-------------|----------|----------------|-----------------| | go.mod | Go | go test | go test -coverprofile=coverage.out ./... | | pyproject.toml or setup.py | Python | pytest | pytest --cov --cov-report=term-missing | | package.json | JS/TS | jest or vitest | npx jest --coverage or npx vitest run --coverage | | Cargo.toml | Rust | cargo test | cargo tarpaulin --out Lcov |
Load /standards for the detected language. Apply all testing conventions from the standards skill (naming, assertion style, structural rules).
Go-specific rules (from project CLAUDE.md):
- Test file naming:
<source>_test.go. NEVERcov*_test.goor*_extra_test.go. - Test function naming:
Test<Uppercase>(e.g.,TestParseConfig_EmptyInput). - Prefer table-driven tests for multi-case functions.
- Use
captureStdoutfor output functions and assert content.
Python-specific rules:
- Use
pytestwithconftest.pyfor shared fixtures. - Use
@pytest.mark.parametrizefor multi-case functions. - Type hints on test helpers.
JS/TS-specific rules:
- Use
describe/itblocks with clear names. - Group by function or module under test.
- Mock external services, not internal code.
Step 1: Analyze Existing Test Coverage
Run the coverage command for the detected language:
# Go
go test -coverprofile=coverage.out ./... 2>&1 | tee .agents/test/coverage-raw.txt
go tool cover -func=coverage.out > .agents/test/coverage-func.txt
# Python
pytest --cov --cov-report=term-missing --cov-report=json:.agents/test/coverage.json 2>&1 | tee .agents/test/coverage-raw.txt
# JS/TS
npx jest --coverage --coverageReporters=text 2>&1 | tee .agents/test/coverage-raw.txt
# Rust
cargo tarpaulin --out Lcov 2>&1 | tee .agents/test/coverage-raw.txt
Parse the output. Build a ranked list of files by coverage percentage (lowest first).
If /complexity is available, cross-reference: high-complexity + low-coverage = highest priority targets.
Step 2: Identify Gaps
From the coverage data, identify:
- Untested files -- source files with no corresponding test file.
- Uncovered functions -- exported/public functions with 0% coverage.
- Uncovered branches -- functions with partial coverage (conditionals, error paths).
- Missing edge cases -- functions that only test the happy path.
Produce a gap list sorted by risk (high complexity + low coverage first):
File | Coverage | Functions Missing Tests | Risk
----------------------------|----------|------------------------|------
internal/parser/parse.go | 23% | ParseConfig, Validate | HIGH
internal/goals/measure.go | 45% | MeasureFitness | MEDIUM
lib/utils.go | 89% | (edge cases only) | LOW
Write gap list to .agents/test/gaps.md.
Step 3: Generate Tests
For each gap (highest risk first), generate tests following language-specific patterns.
If the target needs a specialized test pattern, load the matching reference before writing tests:
- Contract/API/CLI compatibility: [references/conformance-harnesses.md](references/conformance-harnesses.md)
- Parsers, serializers, and untrusted inputs: [references/fuzzing.md](references/fuzzing.md)
- Generated files, snapshots, and rendered output: [references/golden-artifacts.md](references/golden-artifacts.md)
- Metamorphic/invariant-heavy behavior: [references/metamorphic-testing.md](references/metamorphic-testing.md)
- Golden artifact update review: [references/golden-artifact-strategy.md](references/golden-artifact-strategy.md)
- Real databases, services, queues, or APIs where mocks would hide failures: [references/real-service-e2e.md](references/real-service-e2e.md)
Go: Table-Driven Tests
func TestParseConfig_Variants(t *testing.T) {
tests := []struct {
name string
input string
want *Config
wantErr bool
}{
{name: "valid minimal", input: `name: foo`, want: &Config{Name: "foo"}},
{name: "empty input", input: "", want: nil, wantErr: true},
{name: "invalid yaml", input: `: bad`, want: nil, wantErr: true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := ParseConfig(tt.input)
if (err != nil) != tt.wantErr {
t.Errorf("ParseConfig() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("ParseConfig() = %v, want %v", got, tt.want)
}
})
}
}
Python: Parametrized Tests
@pytest.mark.parametrize("input_val,expected", [
("valid", Config(name="valid")),
("", None),
(None, None),
])
def test_parse_config(input_val: str, expected: Config | None) -> None:
result = parse_config(input_val)
assert result == expected
JS/TS: Describe Blocks
describe("parseConfig", () => {
it("parses valid input", () => {
expect(parseConfig("valid")).toEqual({ name: "valid" });
});
it("returns null for empty input", () => {
expect(parseConfig("")).toBeNull();
});
it("throws on malformed input", () => {
expect(() => parseConfig(": bad")).toThrow();
});
});
Generation Rules
- Read the source function first. Understand inputs, outputs, error conditions, and branches.
- Cover every branch. Each
if,switch, error return, and edge case gets at least one test case. - Assert exact expected values. Use
== expected, not!= nilor!= "". - Name tests descriptively. The test name should describe the scenario:
"empty input returns error", not"test1". - Test error paths explicitly. Verify error messages or error types, not just that an error occurred.
- One assertion focus per test case. Each table row or parametrized case tests one specific behavior.
Step 4: Run Generated Tests
After writing each test file, immediately run it:
# Go
go test -v -run TestParseConfig ./internal/parser/
# Python
pytest -xvs tests/test_parser.py
# JS/TS
npx jest --verbose tests/parser.test.ts
# Rust
cargo test test_parse_config -- --nocapture
If tests fail:
- Read the failure output.
- Determine if the test is wrong or the code has a bug.
- If the test is wrong: fix the test assertion or setup.
- If the code has a bug: report it but fix the test to match current behavior, noting the bug in the output.
- Re-run until green.
Never commit failing tests (unless in TDD mode, Step red).
Step 5: Output Coverage Report
Re-run coverage after adding tests:
# Same commands as Step 1
Compare before/after. Write summary to .agents/test/summary.md:
# Test Generation Summary
**Language:** Go
**Date:** 2026-03-27
**Mode:** generate
## Coverage Delta
| Metric | Before | After | Delta |
|--------|--------|-------|-------|
| Overall | 52.3% | 71.8