test-coverage
Test Coverage Analysis Skill
When analyzing test coverage, follow this structured process. The goal is not 100% coverage — it's ensuring the riskiest code is tested well.
1. Discover the Testing Setup
Before analyzing, understand the project's testing landscape:
# Detect testing framework
# Node.js
cat package.json | grep -E "jest|vitest|mocha|ava|tap|playwright|cypress|testing-library"
# Python
cat requirements.txt pyproject.toml setup.cfg 2>/dev/null | grep -E "pytest|unittest|nose|coverage|tox"
# Ruby
cat Gemfile 2>/dev/null | grep -E "rspec|minitest|capybara|factory_bot"
# Go
grep -r "_test.go" --include="*.go" -l .
# Java
cat pom.xml build.gradle 2>/dev/null | grep -E "junit|mockito|testng|jacoco"
# PHP
cat composer.json 2>/dev/null | grep -E "phpunit|pest|mockery"
Identify:
- Testing framework in use (Jest, Vitest, Pytest, RSpec, JUnit, etc.)
- Coverage tool configured (Istanbul/nyc, coverage.py, SimpleCov, JaCoCo, etc.)
- Test directory structure (co-located vs separate test folder)
- Naming conventions (*.test.ts, .spec.ts, test_.py, *_test.go)
- Test types present (unit, integration, e2e, snapshot)
- CI integration (are tests running in CI? is coverage enforced?)
2. Run Existing Coverage
# Node.js (Jest)
npx jest --coverage --coverageReporters=text
# Node.js (Vitest)
npx vitest run --coverage
# Python (Pytest)
python -m pytest --cov=. --cov-report=term-missing
# Go
go test -coverprofile=coverage.out ./...
go tool cover -func=coverage.out
# Ruby (RSpec)
COVERAGE=true bundle exec rspec
# Java (Maven + JaCoCo)
mvn test jacoco:report
# PHP (PHPUnit)
php artisan test --coverage
Record:
- Overall line coverage percentage
- Overall branch coverage percentage
- Files with 0% coverage (completely untested)
- Files with < 50% coverage (poorly tested)
- Uncovered lines (specific line numbers)
3. Identify What's NOT Tested
3a. Find Files Without Tests
# Node.js — find source files without matching test files
find src -name "*.ts" -o -name "*.js" | while read f; do
base=$(basename "$f" | sed 's/\.\(ts\|js\)$//')
if ! find . -name "${base}.test.*" -o -name "${base}.spec.*" | grep -q .; then
echo "NO TEST: $f"
fi
done
# Python — find modules without test files
find src -name "*.py" ! -name "__init__.py" | while read f; do
base=$(basename "$f" .py)
if ! find . -name "test_${base}.py" -o -name "${base}_test.py" | grep -q .; then
echo "NO TEST: $f"
fi
done
# Go — find packages without test files
find . -name "*.go" ! -name "*_test.go" -exec dirname {} \; | sort -u | while read d; do
if ! ls "$d"/*_test.go 2>/dev/null | grep -q .; then
echo "NO TEST: $d"
fi
done
3b. Find Untested Code Paths
Look for these commonly missed patterns:
- Error handlers and catch blocks — the most commonly untested code
- Edge cases — null, undefined, empty arrays, zero, negative numbers, boundary values
- Else branches — the unhappy path in if/else
- Switch default cases — fallback handling
- Early returns and guard clauses — validation at the top of functions
- Timeout and retry logic — what happens when things fail
- Race conditions — concurrent operations
- Cleanup code — finally blocks, destructors, shutdown handlers
- Configuration branches — code that runs differently per environment
- Deprecated or feature-flagged code — code behind flags that's still reachable
3c. Find Dead or Unreachable Code
# Node.js — find unused exports
npx ts-prune
# Python — find unused code
pip install vulture && vulture src/
# General — find functions not referenced anywhere
grep -rn "function\|def\|func " src/ | while read line; do
fname=$(echo "$line" | grep -oP '(?:function|def|func)\s+\K\w+')
count=$(grep -rn "$fname" src/ | wc -l)
if [ "$count" -le 1 ]; then
echo "POSSIBLY UNUSED: $line"
fi
done
4. Risk-Based Prioritization
Not all untested code is equally important. Prioritize by risk:
🔴 Critical — Test These First
- Authentication and authorization — login, signup, password reset, permission checks
- Payment and billing — charge, refund, subscription logic
- Data mutation — create, update, delete operations
- API endpoints — especially public-facing ones
- Input validation — sanitization and parsing of user input
- Security-sensitive code — encryption, token generation, access control
- Core business logic — the main value of your application
🟠 High — Test These Next
- Error handling — catch blocks, error boundaries, fallback behavior
- Database queries — complex queries, transactions, migrations
- Third-party integrations — API calls, webhooks, callbacks
- State management — reducers, stores, state transitions
- File operations — uploads, downloads, processing
- Background jobs — queues, cron jobs, workers
🟡 Medium — Test When Possible
- UI components — interactive components, forms, modals
- Utility functions — helpers, formatters, transformers
- Configuration — environment-specific logic
- Middleware — request/response processing pipeline
- Caching logic — cache invalidation, TTL, fallbacks
🟢 Low — Test If Time Permits
- Static components — presentational components with no logic
- Type definitions — interfaces, types, enums
- Constants and config objects — static values
- Logging — log formatting and output
- Dev-only code — seeders, fixtures, debug utilities
5. Test Quality Analysis
Coverage percentage alone doesn't mean tests are good. Analyze quality:
Assertion Quality
// 🔴 BAD — test runs but asserts nothing meaningful
test('creates user', async () => {
const result = await createUser({ name: 'Alice' });
expect(result).toBeTruthy(); // too vague
});
// ✅ GOOD — specific, meaningful assertions
test('creates user with correct fields', async () => {
const result = await createUser({ name: 'Alice' });
expect(result.id).toBeDefined();
expect(result.name).toBe('Alice');
expect(result.createdAt).toBeInstanceOf(Date);
});
Test Independence
// 🔴 BAD — tests depend on each other's state
let userId;
test('creates user', async () => {
const user = await createUser({ name: 'Alice' });
userId = user.id;
});
test('fetches user', async () => {
const user = await getUser(userId); // depends on previous test
});
// ✅ GOOD — each test sets up its own state
test('fetches user', async () => {
const created = await createUser({ name: 'Alice' });
const fetched = await getUser(created.id);
expect(fetched.name).toBe('Alice');
});
Common Test Smells
- No assertions — test runs code but checks nothing
- Testing implementation, not behavior — brittle tests that break on refactors
- Over-mocking — mocking so much that the test proves nothing
- Flaky tests — tests that pass/fail randomly (timing, order-dependent, network)
- Duplicate tests — same scenario tested multiple times in different places
- Giant test files — 1000+ line test files that are hard to maintain
- Missing cleanup — tests that leave behind state (DB records, files, env vars)
- Snapshot overuse — snapshots accepted without review, hiding regressions
- Copy-paste tests — duplicated setup that should be extracted into helpers
- Happy path only — only testing success, never failure
6. Stack-Specific Checks
Node.js / Jest / Vitest
- Check for missing
afterEachcleanup (open handles, DB connections) - Verify async tests use
awaitor return promises (silent failures otherwise) - Check for missing
jest.mock()cleanup between tests - Look for
setTimeoutin tests withoutjest.useFakeTimers() - Verify snapshot tests are intentional and reviewed
- Check for missing error boundary tests in React components
- Verify
act()wrapping on React state updates in tests - Look for missing
waitFor/findByon async UI updates
Python / Pytest
- Check for missing
conftest.pyfixtures for common setup - Verify database tests use transactions and rollback (
@pytest.mark.django_db) - Look for missing
parametrizeon tests that should cover multiple inputs - Check for missing
mock.patchcleanup (use context managers or decorators) - Verify async tests use
@pytest.mark.asyncio - Check for missing exception tests (
with pytest.raises(...)) - Look for hardcoded file paths in tests (use
tmp_pathfixture) - Verify test isolation — no tests reading/writing shared state
React / Next.js
- Check for missing
rendertests on all user-facing components - Verify form components test validation, submission, and error states
- Look for missing accessibility tests (
@testing-library/jest-dommatchers) - Check for untested loading and error states
- Verify hooks are tested with
renderHookfrom testing-library - Check for missing tests on context providers and consumers
- Look for untested route guards and redirects
- Verify Server Components have integration tests
- Check for missing tests on API routes / Server Actions
Vue / Nuxt
- Check for missing
mount/shallowMounttests on components - Verify Pinia stores are tested with
createTestingPinia - Look for missing
emitted()checks on event emissions - Check for untested computed properties and watchers
- Verify composables are tested independently
- Check for missing tests on Nuxt middleware and plugins
Go
- Check for missing table-driven tests on functions with multiple inputs
- Verify error return values are tested (not just happy path)
- Look for missing
t.Parallel()on independent tests - Check for missing
t.Helper()on test utility functions - Verify interfaces are tested with mock implementations
- Check for missing benchmark tests on performance-critical code (
func BenchmarkX) - Look for missing
t.Cleanup()for resource teardown - Verify HTTP handlers are tested with
httptest.NewServer
Java / Spring Boot
- Check for missing
@SpringBootTestintegration tests - Verify
@MockBeanis used appropriately (not over-mocked) - Look for missing
@Transactionalon database tests (auto rollback) - Check for missing controller tests with
MockMvc - Verify exception handlers are tested
- Check for missing
@ParameterizedTeston multi-input tests - Look for untested
@Scheduledtasks and async methods - Verify repository custom queries have integration tests
Ruby / RSpec
- Check for missing
describeblocks for each public method - Verify
letandbeforeblocks handle proper setup/teardown - Look for missing
contextblocks for different scenarios - Check for missing
shared_examplesfor common behavior - Verify factory definitions cover all required fields (
FactoryBot) - Check for missing request specs on API endpoints
- Look for untested ActiveRecord callbacks and validations
- Verify background jobs (Sidekiq/Resque) have specs
PHP / Laravel
- Check for missing Feature tests on routes and controllers
- Verify database tests use
RefreshDatabaseorDatabaseTransactions - Look for missing tests on form requests (validation rules)
- Check for missing
assertDatabaseHas/assertDatabaseMissingassertions - Verify mail, notification, and event fakes are used
- Check for missing tests on Eloquent scopes and accessors
- Look for untested middleware
- Verify queue jobs and listeners have tests
Mobile (React Native / Flutter)
- Check for missing widget/component tests
- Verify navigation flows are tested
- Look for missing tests on platform-specific code (iOS vs Android)
- Check for untested offline/error states
- Verify async storage operations are tested
- Check for missing tests on deep link handling
- Look for untested permission request flows
- Verify API response parsing is tested with realistic mock data
API / Integration Tests
- Check for missing tests on all HTTP methods (GET, POST, PUT, DELETE, PATCH)
- Verify authentication is tested (valid token, expired token, no token, wrong role)
- Look for missing tests on pagination, filtering, and sorting
- Check for missing tests on rate limiting behavior
- Verify webhook handlers are tested with realistic payloads
- Check for missing tests on file upload/download endpoints
- Look for untested CORS behavior
- Verify error responses match API documentation/contract
Database
- Check for missing migration tests (up and rollback)
- Verify complex queries are tested with realistic data volumes
- Look for missing tests on database constraints (unique, foreign key, check)
- Check for untested transaction behavior (commit, rollback, deadlock)
- Verify connection pooling and timeout handling is tested
- Check for missing seed data validation tests
7. Coverage Improvement Plan
After analysis, provide a prioritized plan:
Immediate (This Sprint)
- List specific files and functions to test first based on risk
- Provide test scaffolding for the highest-priority untested code
- Suggest specific test cases with descriptions
Short-Term (Next 2 Sprints)
- Increase coverage on medium-risk areas
- Add integration tests for critical user flows
- Fix test quality issues (weak assertions, flaky tests)
Long-Term (This Quarter)
- Set up coverage thresholds in CI (fail build if coverage drops)
- Add e2e tests for critical user journeys
- Implement mutation testing to verify test effectiveness
- Set up coverage trend tracking
Output Format
Coverage Report
Overall Coverage:
| Metric | Current | Target | Gap |
|---|---|---|---|
| Line Coverage | X% | 80% | X% |
| Branch Coverage | X% | 70% | X% |
| Function Coverage | X% | 85% | X% |
Untested Files (by risk):
🔴 Critical — No Tests:
src/auth/login.ts— handles user authenticationsrc/payments/charge.ts— processes payments
🟠 High — Partial Coverage:
src/api/users.ts— 40% covered, missing error pathssrc/db/queries.ts— 55% covered, missing edge cases
Test Quality Issues:
tests/user.test.ts:45— assertion too vague (toBeTruthy)tests/order.test.ts— tests are order-dependenttests/api.test.ts:120— over-mocked, not testing real behavior
For Each Untested Area
File: src/auth/login.ts
- Risk: 🔴 Critical
- Why it matters: Handles user authentication — bugs here = security breach
- Missing tests:
- Valid login with correct credentials
- Login with wrong password (should return 401)
- Login with non-existent email (should return 401, same message as wrong password)
- Login with expired account
- Rate limiting after 5 failed attempts
- SQL injection attempt in email field
- Session creation and token generation
- Test scaffold:
describe('login', () => {
it('returns a token for valid credentials', async () => { ... });
it('returns 401 for incorrect password', async () => { ... });
it('returns 401 for non-existent email', async () => { ... });
it('locks account after 5 failed attempts', async () => { ... });
it('rejects SQL injection in email', async () => { ... });
});
Summary
End every analysis with:
- Current state — Overall coverage and quality assessment
- Biggest gaps — The riskiest untested code
- Top 5 tests to write first — Highest impact, with brief descriptions
- Test quality issues — Problems with existing tests that need fixing
- CI recommendations — Coverage thresholds, pre-commit hooks, reporting
- What's done well — Good testing practices already in place to maintain
More from aakash-dhar/claude-skills
security-audit
Scans code for security vulnerabilities including injection attacks, authentication flaws, exposed secrets, insecure dependencies, and data exposure. Use when the user says "security review", "is this secure?", "check for vulnerabilities", "audit this", or before deploying to production.
118pentest-report
Generates a structured penetration testing report based on OWASP standards including OWASP Top 10, ASVS, and WSTG methodology. Scans code for vulnerabilities, maps findings to OWASP categories, assigns CVSS scores, and produces a professional pentest report. Use when the user says "pentest report", "penetration testing", "OWASP audit", "OWASP report", "security assessment", "vulnerability assessment", "application security test", or "OWASP compliance check".
18vulnerability-report
Scans project dependencies for known vulnerabilities (CVEs), categorizes them into three severity-based reports (Critical/High, Medium, Low), and generates detailed markdown documents with remediation guidance. Saves output to project-decisions/ folder. Use when the user says "vulnerability report", "dependency vulnerabilities", "CVE report", "package vulnerabilities", "npm audit report", "dependency scan", "vulnerable packages", "security vulnerabilities in dependencies", or "generate vulnerability reports".
5code-review
Reviews code for bugs, security issues, performance problems, and adherence to best practices. Use when the user asks to "review this code", "check my code", "is this code good?", or before submitting a PR.
4risk-register
Creates and maintains a living project risk register by analyzing the codebase, dependencies, team structure, timeline, and technical decisions. Identifies risks, scores them by likelihood and impact, assigns owners, tracks mitigations, and flags risks that have changed since last assessment. Saves output to project-decisions/ folder. Use when the user says "risk register", "project risks", "what could go wrong", "risk assessment", "identify risks", "update risks", "risk review", "what are our risks", or "flag risks for the project".
4tech-decision
Evaluates technical proposals, "should we do X instead of Y?" questions, tool comparisons, and architecture suggestions. Analyzes feasibility, compares options with structured pros/cons, estimates effort and risk, and provides a clear recommendation. Saves output to project-decisions/ folder. Use when the user says "should we", "what if we", "is it worth", "should we switch to", "compare X vs Y", "evaluate this proposal", "tech decision", or brings up a technical suggestion from a team discussion.
1