| # Coverage Report Interpretation Guide | |
| **Purpose**: Comprehensive guide for interpreting coverage reports and improving test coverage. | |
| **Last Updated**: 2026-02-11 | |
| --- | |
| ## Overview | |
| Code coverage measures how much of your codebase is executed by tests. Coverage reports help identify untested code, track testing progress, and enforce quality standards. | |
| **Key Concepts**: | |
| - **Line Coverage**: Percentage of executable lines executed by tests | |
| - **Branch Coverage**: Percentage of code branches (if/else, try/except) executed by tests | |
| - **Coverage Paradox**: High coverage numbers don't guarantee high quality | |
| **Project Coverage Targets**: | |
| - Overall backend: 80% | |
| - Governance domain: 80% | |
| - Security domain: 80% | |
| - Episodes domain: 80% | |
| --- | |
| ## Coverage Metrics Explained | |
| ### Line Coverage vs Branch Coverage | |
| **Line Coverage** measures which lines of code were executed: | |
| ``` | |
| def check_permission(user, action): | |
| if user.is_admin: # Line 1 | |
| return True # Line 2 | |
| return False # Line 3 | |
| ``` | |
| If tests only call `check_permission(admin_user, "read")`: | |
| - Line coverage: 67% (lines 1-2 executed, line 3 not executed) | |
| - Branch coverage: 50% (only "if" branch tested, "else" branch not tested) | |
| **Branch Coverage** is more accurate because it measures all possible execution paths: | |
| - If statements: 2 branches (true, false) | |
| - Try/except: 2 branches (success, exception) | |
| - Short-circuit operators: 2 branches (or, and) | |
| **Why Branch Coverage Matters**: | |
| ```python | |
| # Line coverage: 100%, Branch coverage: 50% | |
| def divide(a, b): | |
| if b != 0 and a / b > 10: # Both lines executed | |
| return "large" | |
| return "small" | |
| # Test: divide(20, 2) → lines 1-3 executed | |
| # Missing: divide(10, 0) → exception path not tested | |
| ``` | |
| **Recommendation**: Always enable branch coverage with `--cov-branch` flag (already in pytest.ini). | |
| --- | |
| ### Coverage Percentage Interpretation | |
| | Coverage Range | Interpretation | Action | | |
| |----------------|----------------|--------| | |
| | 0-20% | Untested codebase | Start with critical paths | | |
| | 20-50% | Early testing | Focus on core functionality | | |
| | 50-80% | Good coverage | Target for most projects | | |
| | 80-90% | Excellent coverage | Diminishing returns above 80% | | |
| | 90-100% | Near-perfect | Risk of over-testing | | |
| **Diminishing Returns Above 90%**: | |
| - Last 10% costs 50% of testing effort | |
| - Focus on critical paths first | |
| - 100% coverage is rarely practical | |
| **When 80% is Sufficient**: | |
| - Error handling is tested | |
| - Critical paths covered | |
| - Property tests validate invariants | |
| - Integration tests cover workflows | |
| --- | |
| ### Coverage Paradox | |
| **High Coverage, Low Quality**: Tests cover lines but not scenarios. | |
| **Example**: | |
| ```python | |
| # Line coverage: 100%, Branch coverage: 100% | |
| # Quality: LOW (no assertions verify correctness) | |
| def calculate_discount(price, user): | |
| if user.is_vip: | |
| return price * 0.9 | |
| return price | |
| def test_calculate_discount(): | |
| calculate_discount(100, vip_user) # Line covered | |
| calculate_discount(100, regular_user) # Line covered | |
| # No assertions! Bugs would pass silently. | |
| ``` | |
| **Detection Strategies**: | |
| 1. **Property-based tests**: Validate invariants across all inputs (Hypothesis) | |
| 2. **Mutation testing**: Detect weak tests (mutmut) | |
| 3. **Assertion density**: Track assertions per line (conftest.py) | |
| **See**: `property_tests/INVARIANTS.md` for property test patterns. | |
| --- | |
| ### Missing Coverage | |
| **0% Coverage Means**: | |
| - No tests execute this code | |
| - Untested bugs may exist | |
| - Refactoring is risky | |
| **Common Causes**: | |
| 1. **Dead code**: Unused functions (consider removing) | |
| 2. **Error paths**: Exception handlers not tested | |
| 3. **Edge cases**: Boundary conditions not covered | |
| 4. **New code**: Written without tests | |
| **Priority for Improving Coverage**: | |
| 1. **Critical paths**: Authentication, authorization, payments | |
| 2. **Security**: Input validation, encryption, access control | |
| 3. **Data integrity**: Database operations, transactions | |
| 4. **User-facing**: API endpoints, business logic | |
| --- | |
| ## Reading Coverage Reports | |
| ### HTML Report Navigation | |
| **Location**: `backend/tests/coverage_reports/html/index.html` | |
| **Usage**: | |
| ```bash | |
| # Generate HTML report | |
| pytest tests/ --cov=core --cov=api --cov=tools --cov-report=html | |
| # Open in browser | |
| open tests/coverage_reports/html/index.html | |
| ``` | |
| **Interpretation**: | |
| - **Green**: >80% coverage (good) | |
| - **Yellow**: 50-80% coverage (needs improvement) | |
| - **Red**: <50% coverage (critical gaps) | |
| **HTML Report Features**: | |
| - Click directory to see files | |
| - Click file to see line-by-line coverage | |
| - Red lines = not executed | |
| - Yellow lines = partially executed (branch coverage) | |
| --- | |
| ### JSON Report Structure | |
| **Location**: `backend/tests/coverage_reports/metrics/coverage.json` | |
| **Format**: | |
| ```json | |
| { | |
| "meta": { | |
| "timestamp": "2026-02-11T10:30:00", | |
| "branch_coverage": true, | |
| "show_contexts": true | |
| }, | |
| "totals": { | |
| "covered_lines": 2340, | |
| "num_statements": 15000, | |
| "percent_covered": 15.6, | |
| "covered_branches": 1200, | |
| "num_branches": 3000, | |
| "percent_covered_branch": 40.0 | |
| }, | |
| "files": { | |
| "core/agent_governance_service.py": { | |
| "summary": { | |
| "percent_covered": 45.2, | |
| "percent_covered_branch": 30.0 | |
| }, | |
| "executed_lines": [10, 11, 15, 20], | |
| "missing_lines": [25, 30, 35], | |
| "excluded_lines": [1, 2, 3] | |
| } | |
| } | |
| } | |
| ``` | |
| **Key Fields**: | |
| - `percent_covered`: Line coverage percentage | |
| - `percent_covered_branch`: Branch coverage percentage | |
| - `missing_lines`: Line numbers not executed | |
| - `executed_lines`: Line numbers executed | |
| **Usage in CI/CD**: | |
| ```bash | |
| # Extract coverage percentage | |
| COVERAGE=$(jq '.totals.percent_covered' tests/coverage_reports/metrics/coverage.json) | |
| # Fail if below threshold | |
| if (( $(echo "$COVERAGE < 80" | bc -l) )); then | |
| echo "Coverage $COVERAGE% below 80% threshold" | |
| exit 1 | |
| fi | |
| ``` | |
| --- | |
| ### Terminal Report Interpretation | |
| **Generate Terminal Report**: | |
| ```bash | |
| pytest tests/ --cov-report=term-missing | |
| ``` | |
| **Output Format**: | |
| ``` | |
| Name Stmts Miss Cover Missing | |
| ------------------------------------------------------------------------ | |
| core/agent_governance_service.py 150 75 50% 23-45, 89-102 | |
| core/models.py 300 120 60% 156-189, 234-267 | |
| api/auth_routes.py 50 40 20% 12-35, 67-89 | |
| ------------------------------------------------------------------------ | |
| TOTAL 500 235 53% | |
| ``` | |
| **Columns**: | |
| - `Stmts`: Total executable statements | |
| - `Miss`: Statements not executed | |
| - `Cover`: Coverage percentage | |
| - `Missing`: Line ranges not covered | |
| **Color Coding**: | |
| - Green: >80% | |
| - Yellow: 50-80% | |
| - Red: <50% | |
| --- | |
| ## Coverage by Domain | |
| ### Governance Domain | |
| **Target**: 80% | |
| **Current**: 13.37% (as of 2026-02-11) | |
| **Files**: | |
| - `agent_governance_service.py` - Agent lifecycle and permissions | |
| - `agent_context_resolver.py` - Agent resolution | |
| - `governance_cache.py` - High-performance caching | |
| - `trigger_interceptor.py` - Maturity-based trigger routing | |
| **Priority Tests**: | |
| 1. Maturity threshold enforcement (STUDENT agents block high-complexity actions) | |
| 2. Confidence-based routing (low confidence → supervised) | |
| 3. Permission checks (all 4x4 maturity/complexity combinations) | |
| **Impact**: Governance failures cause unauthorized actions or security violations. | |
| --- | |
| ### Security Domain | |
| **Target**: 80% | |
| **Current**: 22.40% (as of 2026-02-11) | |
| **Files**: | |
| - `auth_routes.py` - Authentication endpoints | |
| - `encryption.py` - Data encryption/decryption | |
| - `validation.py` - Input validation | |
| **Priority Tests**: | |
| 1. JWT token validation (expired, invalid, malformed) | |
| 2. Password hashing (bcrypt, salt rounds) | |
| 3. Input validation (OWASP Top 10 payloads) | |
| 4. SQL injection prevention (parameterized queries) | |
| **Impact**: Security failures cause unauthorized access or data breaches. | |
| --- | |
| ### Episodes Domain | |
| **Target**: 80% | |
| **Current**: 15.52% (as of 2026-02-11) | |
| **Files**: | |
| - `episode_segmentation_service.py` - Episode creation | |
| - `episode_retrieval_service.py` - Episode retrieval | |
| - `episode_lifecycle_service.py` - Episode lifecycle | |
| - `agent_graduation_service.py` - Graduation validation | |
| **Priority Tests**: | |
| 1. Time gap detection (boundaries, thresholds) | |
| 2. Semantic retrieval (similarity, ranking) | |
| 3. Graduation criteria (episode count, intervention rate) | |
| 4. Constitutional compliance (intervention tracking) | |
| **Impact**: Episode failures cause incorrect agent learning or premature promotion. | |
| --- | |
| ### Backend Overall | |
| **Target**: 80% | |
| **Current**: 15.57% (as of 2026-02-11) | |
| **Files Tracked**: 401 files | |
| **Breakdown**: | |
| - Governance: 13.37% | |
| - Security: 22.40% | |
| - Episodes: 15.52% | |
| - Other domains: Variable | |
| **Focus Areas**: | |
| 1. Critical paths: Auth, governance, payments | |
| 2. Error handling: Exception paths, edge cases | |
| 3. Integration points: API endpoints, database operations | |
| --- | |
| ## Coverage Trending | |
| ### Tracking Coverage Over Time | |
| **Purpose**: Detect coverage regressions and measure progress. | |
| **Location**: `backend/tests/coverage_reports/trends/coverage_trend.json` | |
| **Format**: | |
| ```json | |
| { | |
| "coverage_history": [ | |
| { | |
| "date": "2026-02-11", | |
| "commit": "abc123", | |
| "overall_percent": 15.57, | |
| "governance_percent": 13.37, | |
| "security_percent": 22.40, | |
| "episodes_percent": 15.52 | |
| } | |
| ], | |
| "targets": { | |
| "overall": 80, | |
| "governance": 80, | |
| "security": 80, | |
| "episodes": 80 | |
| } | |
| } | |
| ``` | |
| **Usage**: | |
| ```bash | |
| # View trend | |
| jq '.coverage_history[] | .date + ": " + (.overall_percent | tostring) + "%"' \ | |
| tests/coverage_reports/trends/coverage_trend.json | |
| # Detect regression | |
| LATEST=$(jq '.coverage_history[0].overall_percent' coverage_trend.json) | |
| PREVIOUS=$(jq '.coverage_history[1].overall_percent' coverage_trend.json) | |
| if (( $(echo "$LATEST < $PREVIOUS" | bc -l) )); then | |
| echo "Coverage regression detected: $PREVIOUS% → $LATEST%" | |
| fi | |
| ``` | |
| --- | |
| ### Detecting Coverage Regressions | |
| **Regression**: Coverage decreases between commits. | |
| **CI/CD Check**: | |
| ```yaml | |
| # .github/workflows/coverage-report.yml | |
| - name: Check coverage regression | |
| run: | | |
| LATEST=$(jq '.coverage_history[0].overall_percent' coverage_trend.json) | |
| PREVIOUS=$(jq '.coverage_history[1].overall_percent' coverage_trend.json) | |
| THRESHOLD=1.0 # 1% tolerance | |
| if (( $(echo "$PREVIOUS - $LATEST > $THRESHOLD" | bc -l) )); then | |
| echo "Coverage regression: $PREVIOUS% → $LATEST%" | |
| exit 1 | |
| fi | |
| ``` | |
| **Common Causes**: | |
| 1. Deleted tests without replacement | |
| 2. Refactored code without updating tests | |
| 3. New code written without tests | |
| --- | |
| ### Coverage Thresholds and Quality Gates | |
| **Quality Gate**: Minimum coverage requirement for PR approval. | |
| **Configuration** (pytest.ini): | |
| ```ini | |
| [pytest] | |
| cov_fail_under = 80 | |
| ``` | |
| **CI/CD Enforcement**: | |
| ```yaml | |
| - name: Run tests with coverage | |
| run: pytest tests/ --cov=core --cov=api --cov=tools --cov-fail-under=80 | |
| ``` | |
| **Per-Domain Thresholds**: | |
| ```python | |
| # conftest.py | |
| def pytest_terminal_summary(terminalreporter): | |
| coverage_data = json.load(open("coverage.json")) | |
| governance_cov = coverage_data['files']['core/agent_governance_service.py']['summary']['percent_covered'] | |
| if governance_cov < 80: | |
| terminalreporter.write_line(f"ERROR: Governance coverage {governance_cov}% below 80%", red=True) | |
| ``` | |
| **Recommendation**: Start with 60% threshold, increase to 80% as coverage improves. | |
| --- | |
| ## Improving Coverage | |
| ### Identifying Uncovered Lines | |
| **Step 1: Generate HTML Report** | |
| ```bash | |
| pytest tests/ --cov=core --cov=api --cov=tools --cov-report=html | |
| open tests/coverage_reports/html/index.html | |
| ``` | |
| **Step 2: Find Red Files** | |
| - Click through directories | |
| - Look for files with <80% coverage | |
| - Click file to see line-by-line breakdown | |
| **Step 3: Analyze Missing Lines** | |
| - Red lines = not executed | |
| - Yellow lines = partially executed (branch coverage) | |
| - Right-click line → "show context" to see why it's not covered | |
| --- | |
| ### Prioritizing Coverage Gaps | |
| **Priority Matrix**: | |
| | Impact | High Coverage | Low Coverage | Action | | |
| |--------|---------------|--------------|--------| | |
| | **Critical** | Error in test | **IMMEDIATE** | Fix tests | | |
| | **High** | Monitor | **HIGH** | Add tests | | |
| | **Medium** | Defer | **MEDIUM** | Add tests | | |
| | **Low** | Accept | **LOW** | Document | | |
| **Critical Paths** (test first): | |
| 1. Authentication: Login, logout, token refresh | |
| 2. Authorization: Permission checks, maturity enforcement | |
| 3. Data integrity: Database transactions, validation | |
| 4. Security: Encryption, input validation, access control | |
| **Example Priority**: | |
| ```python | |
| # CRITICAL: Auth bypass | |
| if user.is_admin: # Branch coverage: 50% → Fix immediately | |
| return sensitive_data | |
| # HIGH: Business logic | |
| if discount > 0.9: # Branch coverage: 75% → Add test | |
| apply_discount() | |
| # MEDIUM: Edge case | |
| if user.preferences.color == "blue": # Branch coverage: 0% → Defer | |
| set_theme("blue") | |
| ``` | |
| --- | |
| ### Writing Tests for Uncovered Code | |
| **Step 1: Understand What's Missing** | |
| ```python | |
| # Original code (lines 25-30 not covered) | |
| def delete_agent(agent_id, user): | |
| if not user.is_admin: # Line 25 | |
| raise PermissionError() # Line 26 | |
| agent = db.get(agent_id) # Line 27 | |
| if agent.status == "ACTIVE": # Line 28 | |
| raise ValueError() # Line 29 | |
| db.delete(agent) # Line 30 | |
| ``` | |
| **Step 2: Write Test for Missing Path** | |
| ```python | |
| def test_delete_agent_requires_admin(unique_resource_name): | |
| """Test non-admin cannot delete agents (line 25-26).""" | |
| user = UserFactory.create(role="member") # Not admin | |
| agent = AgentFactory.create(id=unique_resource_name) | |
| with pytest.raises(PermissionError): | |
| delete_agent(agent.id, user) | |
| def test_delete_active_agent_fails(unique_resource_name): | |
| """Test deleting active agent raises error (line 28-29).""" | |
| admin = UserFactory.create(role="admin") | |
| agent = AgentFactory.create(id=unique_resource_name, status="ACTIVE") | |
| with pytest.raises(ValueError): | |
| delete_agent(agent.id, admin) | |
| def test_delete_inactive_agent_succeeds(unique_resource_name): | |
| """Test deleting inactive agent works (line 30).""" | |
| admin = UserFactory.create(role="admin") | |
| agent = AgentFactory.create(id=unique_resource_name, status="INACTIVE") | |
| delete_agent(agent.id, admin) | |
| assert db.get(agent.id) is None | |
| ``` | |
| **Step 3: Run Tests** | |
| ```bash | |
| pytest tests/test_agent_service.py -v --cov=core/agent_service --cov-report=term-missing | |
| ``` | |
| **Step 4: Verify Coverage Improved** | |
| - Check HTML report | |
| - Lines 25-30 should now be green | |
| --- | |
| ### When NOT to Chase 100% Coverage | |
| **Exclusions from Coverage Targets**: | |
| 1. **Generated Code**: | |
| - Protobuf-generated files | |
| - OpenAPI-generated models | |
| - Database migration scripts | |
| 2. **Configuration**: | |
| - Constants, enums | |
| - Environment variable loading | |
| - Logging configuration | |
| 3. **Third-Party Wrappers**: | |
| - Thin wrappers around libraries | |
| - Adapter classes (1-2 lines) | |
| 4. **Impossible Paths**: | |
| - OS-specific code (Windows tests on Mac) | |
| - Hardware-dependent code | |
| - GUI code in CI/CD | |
| **Example .coveragerc**: | |
| ```ini | |
| [report] | |
| exclude_lines = | |
| pragma: no cover | |
| def __repr__ | |
| raise AssertionError | |
| raise NotImplementedError | |
| if __name__ == .__main__.: | |
| if TYPE_CHECKING: | |
| @abstractmethod | |
| ``` | |
| **Guideline**: 80% coverage is practical. 100% coverage is rarely worth the cost. | |
| --- | |
| ## Coverage Tools Reference | |
| ### pytest-cov Command Reference | |
| **Basic Usage**: | |
| ```bash | |
| # Run tests with coverage | |
| pytest tests/ --cov=core | |
| # Multiple modules | |
| pytest tests/ --cov=core --cov=api --cov=tools | |
| # Generate reports | |
| pytest tests/ --cov=core --cov-report=html --cov-report=json --cov-report=term | |
| ``` | |
| **Report Formats**: | |
| - `--cov-report=html`: HTML report (coverage_reports/html/index.html) | |
| - `--cov-report=json`: JSON report (coverage.json) | |
| - `--cov-report=term`: Terminal output | |
| - `--cov-report=term-missing`: Terminal with missing lines | |
| **Branch Coverage**: | |
| ```bash | |
| pytest tests/ --cov=core --cov-branch | |
| ``` | |
| **Coverage Fail Threshold**: | |
| ```bash | |
| pytest tests/ --cov=core --cov-fail-under=80 | |
| ``` | |
| **Source Configuration**: | |
| ```bash | |
| pytest tests/ --cov=core --cov-context=test | |
| ``` | |
| --- | |
| ### Coverage.py CLI Reference | |
| **Combine Coverage Data**: | |
| ```bash | |
| # Run tests in parallel | |
| pytest tests/ -n auto --cov=core --cov-parallel | |
| # Combine .coverage files | |
| coverage combine | |
| ``` | |
| **Generate Report After Tests**: | |
| ```bash | |
| pytest tests/ --cov=core | |
| coverage html # Generate HTML | |
| coverage report # Generate terminal | |
| coverage json # Generate JSON | |
| ``` | |
| **Debug Coverage**: | |
| ```bash | |
| coverage debug sys | |
| coverage debug config | |
| ``` | |
| **Erase Coverage Data**: | |
| ```bash | |
| coverage erase # Delete .coverage file | |
| ``` | |
| --- | |
| ### Codecov/Coveralls Integration | |
| **Codecov Upload** (via GitHub Actions): | |
| ```yaml | |
| - name: Upload to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| files: ./tests/coverage_reports/metrics/coverage.json | |
| flags: backend | |
| name: backend-coverage | |
| fail_ci_if_error: false # Don't fail CI if Codecov is down | |
| ``` | |
| **Coveralls Upload** (alternative): | |
| ```yaml | |
| - name: Upload to Coveralls | |
| uses: coverallsapp/github-action@v2 | |
| with: | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| path-to-lcov: ./coverage.lcov | |
| ``` | |
| **Benefits**: | |
| - Historical trending dashboard | |
| - PR comments with coverage diff | |
| - File-by-file coverage breakdown | |
| - Coverage badges for README | |
| **Alternative**: Git-tracked JSON (no external service needed) | |
| --- | |
| ## Related Documentation | |
| - **[TEST_ISOLATION_PATTERNS.md](./TEST_ISOLATION_PATTERNS.md)** - Test isolation patterns and examples | |
| - **[FLAKY_TEST_GUIDE.md](./FLAKY_TEST_GUIDE.md)** - Flaky test prevention and fixing | |
| - **[../property_tests/INVARIANTS.md](../property_tests/INVARIANTS.md)** - Property test invariants | |
| - **[../factories/README.md](../factories/README.md)** - Test data factory usage | |
| - **[../TESTING_GUIDE.md](../TESTING_GUIDE.md)** - Comprehensive testing guide | |
| --- | |
| ## Summary | |
| **Key Takeaways**: | |
| 1. **Branch coverage > Line coverage**: Always use `--cov-branch` | |
| 2. **80% is practical**: Diminishing returns above 90% | |
| 3. **Coverage paradox**: High coverage ≠ high quality (use property tests) | |
| 4. **Trending matters**: Track coverage over time to detect regressions | |
| 5. **Prioritize critical paths**: Auth, governance, security first | |
| **Quick Reference**: | |
| ```bash | |
| # Generate all reports | |
| pytest tests/ --cov=core --cov=api --cov=tools \ | |
| --cov-branch \ | |
| --cov-report=html \ | |
| --cov-report=json \ | |
| --cov-report=term-missing | |
| # Check coverage trend | |
| jq '.coverage_history[0]' tests/coverage_reports/trends/coverage_trend.json | |
| # View HTML report | |
| open tests/coverage_reports/html/index.html | |
| ``` | |
| **Next Steps**: | |
| 1. Generate coverage report for your domain | |
| 2. Identify files <80% coverage | |
| 3. Write tests for critical paths first | |
| 4. Track coverage in CI/CD | |
| 5. Review coverage trends weekly | |
| ## See Also | |
| ### Platform-Specific Testing Guides | |
| - [Frontend Testing Guide](../../docs/FRONTEND_TESTING_GUIDE.md) - Jest, React Testing Library, MSW patterns | |
| - [Mobile Testing Guide](../../docs/MOBILE_TESTING_GUIDE.md) - jest-expo, React Native Testing Library | |
| - [Desktop Testing Guide](../../docs/DESKTOP_TESTING_GUIDE.md) - cargo test, proptest, tarpaulin | |
| ### Related Documentation | |
| - [Testing Documentation Index](../../docs/TESTING_INDEX.md) - Central hub for all testing documentation | |
| - [Testing Onboarding Guide](../../docs/TESTING_ONBOARDING.md) - 15-minute quick start for all platforms | |
| - [Coverage Trending Guide](COVERAGE_TRENDING_GUIDE.md) - 30-day trending and regression detection | |
| - [Flaky Test Quarantine](FLAKY_TEST_QUARANTINE.md) - Multi-run flaky detection and tracking | |