coverage-analysis
About
This skill analyzes test coverage using Vitest's V8 provider to generate reports and identify untested code paths. It helps developers improve test quality by checking line, branch, function, and statement coverage across packages. Use it when setting coverage thresholds, preparing for production, or during code reviews to ensure code quality.
Documentation
Coverage Analysis Skill
This skill helps you analyze and improve test coverage across the monorepo using Vitest's V8 coverage provider.
When to Use This Skill
- Analyzing test coverage across packages
- Identifying untested code paths
- Setting coverage thresholds
- Generating coverage reports
- Improving test quality
- Pre-deployment coverage checks
- Code review coverage validation
Coverage Overview
The project uses Vitest with V8 coverage provider for:
- Line coverage: Percentage of lines executed
- Branch coverage: Percentage of conditional branches tested
- Function coverage: Percentage of functions called
- Statement coverage: Percentage of statements executed
Configuration
Vitest Coverage Config
// vitest.config.ts (root or package-level)
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
globals: true,
environment: "node", // or "jsdom" for frontend
coverage: {
provider: "v8",
reporter: ["text", "json", "html", "lcov"],
reportsDirectory: "./coverage",
exclude: [
"node_modules/",
"__tests__/",
"**/*.test.ts",
"**/*.spec.ts",
"dist/",
"build/",
"*.config.ts",
"*.config.js",
".next/",
".turbo/",
],
include: ["src/**/*.ts", "src/**/*.tsx"],
all: true,
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
},
});
Package-Specific Configs
API Package:
// apps/api/vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
thresholds: {
lines: 85, // Higher threshold for backend
functions: 85,
branches: 80,
statements: 85,
},
exclude: [
"__tests__/",
"src/index.ts", // Exclude entry point
"src/config/**", // Exclude config files
],
},
},
});
Web Package:
// apps/web/vitest.config.ts
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
test: {
environment: "jsdom",
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
thresholds: {
lines: 75, // Frontend may have lower threshold
functions: 75,
branches: 70,
statements: 75,
},
exclude: [
"__tests__/",
"src/app/**", // Exclude Next.js app directory
"**/*.config.*",
],
},
},
});
Database Package:
// packages/database/vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
coverage: {
provider: "v8",
reporter: ["text", "json", "html"],
thresholds: {
lines: 90, // Very high threshold for critical package
functions: 90,
branches: 85,
statements: 90,
},
include: ["src/**/*.ts"],
exclude: [
"__tests__/",
"migrations/**", // Exclude migrations
],
},
},
});
Running Coverage
Common Commands
# Generate coverage for all packages
pnpm test:coverage
# Generate coverage for specific package
pnpm -F @sgcarstrends/api test:coverage
pnpm -F @sgcarstrends/web test:coverage
pnpm -F @sgcarstrends/database test:coverage
# Generate coverage with specific reporters
pnpm test:coverage -- --coverage.reporter=html
pnpm test:coverage -- --coverage.reporter=lcov
# Run tests and generate coverage in watch mode
pnpm test:watch -- --coverage
# Generate coverage for changed files only
pnpm test:coverage -- --changed
Package.json Scripts
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:coverage:ui": "vitest --ui --coverage"
}
}
Coverage Reports
Text Report
# Terminal output
pnpm test:coverage
# Example output:
# ----------|---------|----------|---------|---------|-------------------
# File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
# ----------|---------|----------|---------|---------|-------------------
# All files | 87.5 | 83.33 | 85.71 | 87.5 |
# cars.ts | 90 | 85 | 100 | 90 | 45-47
# coe.ts | 85 | 80 | 75 | 85 | 23, 56-58
# ----------|---------|----------|---------|---------|-------------------
HTML Report
# Generate HTML report
pnpm test:coverage
# Open in browser
open coverage/index.html # macOS
xdg-open coverage/index.html # Linux
start coverage/index.html # Windows
# HTML report features:
# - Interactive file browser
# - Line-by-line coverage visualization
# - Color-coded coverage (green = covered, red = uncovered)
# - Branch coverage details
JSON Report
# Generate JSON report
pnpm test:coverage -- --coverage.reporter=json
# Output: coverage/coverage-final.json
{
"path/to/file.ts": {
"lines": { "1": 1, "2": 1, "3": 0 },
"functions": { "functionName": 1 },
"branches": { "0": [1, 0] },
"statements": { "1": 1, "2": 1 }
}
}
LCOV Report
# Generate LCOV format (for CI tools like Codecov, Coveralls)
pnpm test:coverage -- --coverage.reporter=lcov
# Output: coverage/lcov.info
Coverage Thresholds
Setting Thresholds
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
thresholds: {
// Global thresholds
lines: 80,
functions: 80,
branches: 80,
statements: 80,
// Per-file thresholds
perFile: true,
// Fail build if below thresholds
100: false, // Don't require 100% coverage
},
},
},
});
Enforcing Thresholds in CI
# .github/workflows/test.yml
name: Test
on: [push, pull_request]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install
- run: pnpm test:coverage
# Fail if coverage below threshold
- name: Check coverage
run: |
if [ $(jq '.total.lines.pct' coverage/coverage-summary.json | cut -d. -f1) -lt 80 ]; then
echo "Coverage below 80%"
exit 1
fi
Analyzing Coverage
Identify Untested Files
# Generate coverage for all files (including untested)
pnpm test:coverage -- --coverage.all=true
# Find files with 0% coverage
grep -r '"pct": 0' coverage/coverage-final.json
Find Uncovered Lines
# Generate HTML report and inspect
pnpm test:coverage
open coverage/index.html
# Look for red-highlighted lines in HTML report
# These are uncovered lines that need tests
Check Branch Coverage
// Example: Find uncovered branches
function processData(data: any) {
// Branch 1: if condition
if (data.value > 10) {
return "high";
}
// Branch 2: else condition (uncovered)
return "low";
}
// Test both branches
describe("processData", () => {
it("should return high for values > 10", () => {
expect(processData({ value: 15 })).toBe("high");
});
it("should return low for values <= 10", () => {
expect(processData({ value: 5 })).toBe("low");
});
});
Improving Coverage
Strategy 1: Test Untested Functions
// Find untested function
export function calculateCOEPrice(quota: number, bids: number): number {
// Untested
return quota > 0 ? bids / quota : 0;
}
// Add test
describe("calculateCOEPrice", () => {
it("should calculate price when quota is positive", () => {
expect(calculateCOEPrice(100, 50000)).toBe(500);
});
it("should return 0 when quota is 0", () => {
expect(calculateCOEPrice(0, 50000)).toBe(0);
});
});
Strategy 2: Test Error Paths
// Original: Only happy path tested
export async function fetchCarData(month: string) {
const res = await fetch(`/api/cars?month=${month}`);
return res.json(); // What if fetch fails?
}
// Improved: Test error path
describe("fetchCarData", () => {
it("should fetch data successfully", async () => {
// Happy path test
});
it("should handle network errors", async () => {
vi.spyOn(global, "fetch").mockRejectedValue(new Error("Network error"));
await expect(fetchCarData("2024-01")).rejects.toThrow("Network error");
});
it("should handle non-200 responses", async () => {
vi.spyOn(global, "fetch").mockResolvedValue({
ok: false,
status: 500,
} as Response);
await expect(fetchCarData("2024-01")).rejects.toThrow();
});
});
Strategy 3: Test Edge Cases
// Original: Basic test
export function formatMonth(date: Date): string {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`;
}
// Improved: Test edge cases
describe("formatMonth", () => {
it("should format single-digit months", () => {
expect(formatMonth(new Date("2024-01-01"))).toBe("2024-01");
});
it("should format double-digit months", () => {
expect(formatMonth(new Date("2024-12-01"))).toBe("2024-12");
});
it("should handle leap years", () => {
expect(formatMonth(new Date("2024-02-29"))).toBe("2024-02");
});
});
Strategy 4: Test Conditional Branches
// Function with multiple branches
export function getVehicleCategory(type: string): string {
if (type === "car") return "Category A";
if (type === "motorcycle") return "Category B";
if (type === "taxi") return "Category C";
return "Unknown"; // Often forgotten!
}
// Test all branches
describe("getVehicleCategory", () => {
it.each([
["car", "Category A"],
["motorcycle", "Category B"],
["taxi", "Category C"],
["bus", "Unknown"],
])("should return %s for %s", (type, expected) => {
expect(getVehicleCategory(type)).toBe(expected);
});
});
Coverage in CI/CD
GitHub Actions Integration
# .github/workflows/coverage.yml
name: Coverage
on: [push, pull_request]
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: "pnpm"
- run: pnpm install
- run: pnpm test:coverage
# Upload coverage to Codecov
- uses: codecov/codecov-action@v3
with:
files: ./coverage/lcov.info
flags: unittests
name: codecov-umbrella
# Upload coverage as artifact
- uses: actions/upload-artifact@v4
with:
name: coverage-report
path: coverage/
Coverage Badges
<!-- README.md -->
[](https://codecov.io/gh/username/repo)
Coverage Exclusions
Exclude Specific Code
// Exclude line
/* v8 ignore next */
console.log("Debug statement");
// Exclude block
/* v8 ignore start */
if (process.env.NODE_ENV === "development") {
console.log("Development only");
}
/* v8 ignore stop */
// Exclude function
/* v8 ignore next 5 */
function debugHelper() {
// This entire function is excluded
console.log("Debug");
}
Exclude Files/Directories
// vitest.config.ts
export default defineConfig({
test: {
coverage: {
exclude: [
// Test files
"**/__tests__/**",
"**/*.test.ts",
"**/*.spec.ts",
// Config files
"**/*.config.ts",
"**/*.config.js",
// Build output
"dist/**",
"build/**",
".next/**",
// Specific files
"src/index.ts",
"src/generated/**",
// External dependencies
"node_modules/**",
],
},
},
});
Monorepo Coverage
Aggregate Coverage
# Generate coverage for all packages
pnpm -r test:coverage
# Merge coverage reports (requires custom script)
node scripts/merge-coverage.js
Custom Merge Script
// scripts/merge-coverage.ts
import { readFileSync, writeFileSync } from "fs";
import { glob } from "glob";
const coverageFiles = glob.sync("**/coverage/coverage-final.json", {
ignore: ["node_modules/**"],
});
const merged: any = {};
for (const file of coverageFiles) {
const coverage = JSON.parse(readFileSync(file, "utf-8"));
Object.assign(merged, coverage);
}
writeFileSync("coverage-merged.json", JSON.stringify(merged, null, 2));
console.log("Coverage merged successfully");
Best Practices
1. Set Realistic Thresholds
// ❌ Too strict (100% is often impractical)
thresholds: {
lines: 100,
functions: 100,
branches: 100,
statements: 100,
}
// ✅ Realistic and achievable
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
}
2. Exclude Generated Code
// vitest.config.ts
coverage: {
exclude: [
"src/generated/**",
"*.config.*",
"__tests__/**",
],
}
3. Focus on Critical Paths
// Prioritize testing:
// 1. Business logic
// 2. Data transformations
// 3. API endpoints
// 4. Error handling
// Less critical:
// - UI components (test functionality, not styling)
// - Configuration files
// - Type definitions
4. Track Coverage Over Time
# Store coverage in git (add to .gitignore exceptions)
!coverage/coverage-summary.json
# Track changes
git diff coverage/coverage-summary.json
Troubleshooting
Coverage Not Generated
# Issue: No coverage directory created
# Solution: Ensure tests are running
pnpm test # First run tests
pnpm test:coverage # Then generate coverage
Low Coverage Despite Tests
# Issue: Coverage config excludes tested files
# Solution: Check exclude patterns
# vitest.config.ts
coverage: {
exclude: [
// Remove overly broad patterns
// "src/**", // ❌ This excludes everything!
],
}
Coverage Report Empty
# Issue: Tests passing but coverage 0%
# Solution: Ensure coverage.all is true
coverage: {
all: true, // Include all source files
include: ["src/**/*.ts"],
}
Threshold Failures
# Issue: Coverage below threshold
# Solution: Add missing tests or adjust thresholds
# Lower threshold temporarily
thresholds: {
lines: 70, // Reduced from 80
}
# Or add tests to increase coverage
References
- Vitest Coverage: https://vitest.dev/guide/coverage
- V8 Coverage: https://v8.dev/blog/javascript-code-coverage
- Istanbul (alternative): https://istanbul.js.org
- Related files:
vitest.config.ts- Coverage configuration- Root CLAUDE.md - Testing guidelines
Best Practices Summary
- Set Realistic Thresholds: 80% is good, 100% is often impractical
- Exclude Non-Critical Code: Config files, generated code, tests
- Focus on Critical Paths: Business logic, APIs, error handling
- Test All Branches: Ensure conditional logic is tested
- Track Over Time: Monitor coverage trends
- Use HTML Reports: Visualize uncovered lines
- Integrate with CI: Enforce thresholds in pipelines
- Don't Game Coverage: Write meaningful tests, not just for coverage
Quick Install
/plugin add https://github.com/sgcarstrends/sgcarstrends/tree/main/coverage-analysisCopy and paste this command in Claude Code to install this skill
GitHub 仓库
Related Skills
evaluating-llms-harness
TestingThis Claude Skill runs the lm-evaluation-harness to benchmark LLMs across 60+ standardized academic tasks like MMLU and GSM8K. It's designed for developers to compare model quality, track training progress, or report academic results. The tool supports various backends including HuggingFace and vLLM models.
webapp-testing
TestingThis Claude Skill provides a Playwright-based toolkit for testing local web applications through Python scripts. It enables frontend verification, UI debugging, screenshot capture, and log viewing while managing server lifecycles. Use it for browser automation tasks but run scripts directly rather than reading their source code to avoid context pollution.
finishing-a-development-branch
TestingThis skill helps developers complete finished work by verifying tests pass and then presenting structured integration options. It guides the workflow for merging, creating PRs, or cleaning up branches after implementation is done. Use it when your code is ready and tested to systematically finalize the development process.
go-test
MetaThe go-test skill provides expertise in Go's standard testing package and best practices. It helps developers implement table-driven tests, subtests, benchmarks, and coverage strategies while following Go conventions. Use it when writing test files, creating mocks, detecting race conditions, or organizing integration tests in Go projects.
