| name | improve-test-coverage |
| description | Diagnose and fix test coverage gaps. Identifies uncovered code paths, adds targeted tests, refactors untestable code, and introduces mocking where needed. Never lowers thresholds. |
| argument-hint | [file path, package name, or leave blank to analyze full project] |
Systematically improve test coverage to meet or exceed the project's coverage threshold. This skill diagnoses why coverage is low and applies the right technique for each gap โ adding tests, introducing mocks, refactoring untestable code, or identifying missing requirement-level tests.
Hard rule: Never lower coverage thresholds. The threshold is the goal. The code and tests change to meet it, not the other way around.
Input
The user may provide: $ARGUMENTS (a file path, package name, or specific area to focus on)
If no arguments, analyze the full project's coverage report.
Process
Phase 1: Discover Tooling
-
Find project instructions
- Check
CLAUDE.md then AGENTS.md for coverage commands
- If neither exists, discover from project files (
Cargo.toml, package.json, pyproject.toml, go.mod, etc.)
-
Identify the coverage tool and threshold
- Find the configured coverage threshold (line and branch)
- Find the command to generate a coverage report with per-file or per-function detail
- Common tools:
- Rust:
cargo tarpaulin or cargo llvm-cov
- Go:
go test -coverprofile=coverage.out ./... + go tool cover -func=coverage.out
- Python:
pytest --cov --cov-report=term-missing
- TypeScript/JS:
jest --coverage or vitest --coverage
- Java/Kotlin: JaCoCo report
-
Identify available mocking/testing libraries
- Check dependencies for mocking frameworks already in use
- Common ones:
mockall (Rust), gomock/testify (Go), unittest.mock/pytest-mock (Python), jest.mock/vitest (JS/TS), Mockito (Java)
- Note if none are present โ may need to recommend one
Phase 2: Generate Coverage Report
-
Run the coverage tool with detailed output (per-file, per-function, with line-level miss reporting)
-
Parse the results into a ranked list:
- Current overall coverage (line and branch)
- Target coverage threshold
- Gap: how many percentage points below target
- Per-file coverage, sorted worst-first
- Uncovered line ranges per file
-
If $ARGUMENTS specifies a file or package, focus the analysis on that scope. Otherwise, prioritize files with the largest coverage gaps that are also the most impactful (most uncovered lines, not just lowest percentage).
-
Present the coverage summary to the user:
Current: XX.X% line / XX.X% branch
Target: XX.X% line / XX.X% branch
Gap: X.X% line / X.X% branch (~N uncovered lines)
Worst files:
1. path/to/file.ext โ XX% (N uncovered lines)
2. path/to/other.ext โ XX% (N uncovered lines)
...
Phase 3: Diagnose Coverage Gaps
For each file with significant coverage gaps, read the file and classify each uncovered region into one of these categories:
Category A: Missing Unit Tests
Code that is straightforward to test but simply has no tests yet. Pure logic, data transformations, validators, parsers โ these just need tests written.
Signal: Functions with no corresponding test, simple branching logic, utility functions.
Category B: Untestable Code โ Needs Refactoring
Code that is hard to test because it tightly couples business logic with I/O, external services, or framework internals. The code needs to be restructured to separate testable logic from side effects.
Signal: Functions that mix computation with database calls, HTTP requests, file I/O, or global state. Functions longer than ~50 lines that do multiple things. God functions.
Category C: Needs Mocking
Code that interacts with external dependencies (databases, APIs, file systems, clocks, random number generators) where the dependency should be mocked or stubbed for unit testing.
Signal: Interface/trait method calls to external services, repository pattern implementations, API client usage, system clock reads.
Category D: Missing Requirement-Level Tests
Logic that implements a specific functional requirement (FR-x.x.x) but lacks tests that validate the requirement's acceptance criteria. These are often the most valuable tests to add.
Signal: Cross-reference with design/requirements.md โ look for FR-x.x.x IDs that have implementation code but no test that exercises the requirement's expected behavior.
Category E: Defensive/Error Paths
Error handling branches, edge cases, boundary conditions, and defensive code paths that are never exercised by tests.
Signal: catch/except/match Err blocks, if err != nil branches, input validation failures, timeout paths, retry exhaustion paths.
Present the diagnosis before proceeding:
## Coverage Gap Diagnosis
### path/to/file.ext (42% โ target 95%)
| Lines | Category | Description | Effort |
|-------|----------|-------------|--------|
| 45-62 | A: Missing tests | `validate_input()` โ no tests at all | Low |
| 78-120 | B: Needs refactoring | `process_order()` mixes validation with DB writes | Medium |
| 130-145 | C: Needs mocking | `fetch_user_profile()` calls external API | Medium |
| 150-180 | D: Missing requirement test | FR-2.3.1 โ order cancellation flow untested | Medium |
| 190-195 | E: Error path | Timeout handling in `retry_request()` | Low |
Use AskUserQuestion if the diagnosis reveals a large number of gaps: ask the user which categories or files to prioritize.
Phase 4: Fix Coverage Gaps
Work through the diagnosed gaps, prioritized by:
- Category A (missing tests) first โ highest coverage gain per effort
- Category E (error paths) next โ usually quick to add
- Category D (requirement tests) โ high value, may catch real bugs
- Category C (mocking) โ medium effort, unblocks hard-to-test code
- Category B (refactoring) โ highest effort, save for last
For each gap, apply the appropriate technique:
Technique: Add Missing Tests (Category A)
- Read the function under test
- Identify the input/output contract
- Write tests covering: happy path, edge cases, boundary values
- Run tests to verify they pass and cover the target lines
Technique: Refactor for Testability (Category B)
- Identify the tightly-coupled concern (I/O, external call, framework dependency)
- Extract the pure logic into a separate function that takes inputs and returns outputs
- The original function becomes a thin wrapper that calls the pure function
- Write tests for the extracted pure function
- Run the full test suite to confirm no regressions
Example pattern:
// Before: untestable
fn process_order(order_id: &str) {
let order = db.get_order(order_id); // I/O
let total = calculate_total(&order); // Logic
db.save_total(order_id, total); // I/O
}
// After: testable
fn calculate_total(order: &Order) -> Money { // Pure, testable
// ...
}
fn process_order(order_id: &str) { // Thin wrapper
let order = db.get_order(order_id);
let total = calculate_total(&order);
db.save_total(order_id, total);
}
// Test calculate_total directly
Technique: Introduce Mocking (Category C)
- Check if a mocking library is already in the project's dependencies
- If not, recommend one appropriate for the language and ask the user before adding it
- Create mock implementations of the external dependency's interface/trait
- Write tests that inject the mock and verify behavior
- Verify tests pass and cover the target lines
Do not mock everything. Only mock external boundaries (network, disk, database, clock). Do not mock internal modules โ test those with real instances.
Technique: Add Requirement Tests (Category D)
- Read
design/requirements.md to find the FR-x.x.x specification
- Identify the acceptance criteria or expected behavior
- Write tests that exercise the requirement end-to-end at the unit level
- Name tests to reference the requirement ID (e.g.,
test_fr_2_3_1_order_cancellation)
- These tests often expose real bugs โ if they fail, fix the implementation
Technique: Exercise Error Paths (Category E)
- Identify the error condition that triggers the uncovered branch
- Write a test that forces that condition (invalid input, simulated failure, boundary value)
- Assert that the error is handled correctly (right error type, right message, resources cleaned up)
Phase 5: Verify Improvement
After applying fixes:
-
Run the full coverage report again
-
Compare before and after:
Before: XX.X% line / XX.X% branch
After: XX.X% line / XX.X% branch
Target: XX.X% line / XX.X% branch
Delta: +X.X% line / +X.X% branch
-
Run the full test suite to confirm no regressions
-
Run linter and formatter to clean up new test code
-
If still below threshold:
- Identify remaining gaps
- If diminishing returns (e.g., generated code, trivial getters), explain what's left and why it's hard to cover
- Suggest specific exclusion patterns only for genuinely untestable code (e.g.,
// coverage:ignore for FFI bindings, generated code)
- Never suggest lowering the threshold
Phase 6: Present Results
Report to the user:
- Coverage improvement โ before/after numbers
- What was done โ summary of techniques applied per file
- Tests added โ count of new test functions/cases
- Refactoring done โ any code restructured for testability
- Mocking introduced โ any new mock implementations
- Remaining gaps โ anything still below target and why
- Requirement coverage โ any FR-x.x.x IDs that now have test coverage they didn't before
Important Guidelines
- Never lower thresholds. This is the most important rule. The threshold is the standard. Suggest every other option first.
- Never delete or skip tests to change coverage numbers.
- Never add meaningless tests (e.g., testing that a getter returns a field). Every test should verify behavior that matters.
- Prefer real tests over mocks. Only mock at external boundaries. Internal code should be tested with real instances.
- Refactoring is a valid technique. If code is untestable, fix the code, don't give up on testing it.
- Reference requirements. When
design/requirements.md exists, use it to identify high-value tests that verify actual business behavior.
- Be honest about what's left. If genuinely untestable code remains (FFI, generated code, platform-specific paths), explain why rather than writing fake tests.
Output
After completion:
- Coverage improved toward or past the threshold
- New tests added following TDD principles
- Any refactoring done to improve testability
- Coverage report showing before/after numbers
- Summary of remaining gaps (if any) with justification