| # Mutation Testing Guide | |
| **Last Updated:** February 7, 2026 | |
| **Version:** 1.0.0 | |
| --- | |
| ## Table of Contents | |
| 1. [What is Mutation Testing?](#what-is-mutation-testing) | |
| 2. [Why Mutation Testing?](#why-mutation-testing) | |
| 3. [Quick Start](#quick-start) | |
| 4. [Configuration](#configuration) | |
| 5. [Running Mutation Tests](#running-mutation-tests) | |
| 6. [Interpreting Results](#interpreting-results) | |
| 7. [Quality Gates](#quality-gates) | |
| 8. [Best Practices](#best-practices) | |
| --- | |
| ## What is Mutation Testing? | |
| Mutation testing is a technique for evaluating the quality of your test suite. It makes small changes (mutations) to your code and checks if your tests can detect them. | |
| ### How It Works | |
| 1. **Generate Mutants**: Mutmut introduces small bugs (mutations) into your code | |
| ```python | |
| # Original code | |
| if user.is_admin: | |
| grant_access() | |
| # Mutated code (conditional boundary mutation) | |
| if not user.is_admin: # Changed: if → if not | |
| grant_access() | |
| ``` | |
| 2. **Run Tests**: Mutmut runs your test suite against each mutant | |
| 3. **Classify Results**: | |
| - **Killed**: Tests detected the mutation (tests failed) | |
| - **Survived**: Tests didn't detect the mutation (tests still passed) | |
| - **Timeout**: Tests hung (potential performance issue) | |
| 4. **Calculate Score**: | |
| ``` | |
| Mutation Score = (Killed Mutants / Total Mutants) × 100 | |
| ``` | |
| ### Common Mutation Types | |
| | Type | Example | Description | | |
| |------|---------|-------------| | |
| | Arithmetic | `x + y` → `x - y` | Change operator | | |
| | Boolean | `a and b` → `a or b` | Change logic | | |
| | Comparison | `x < y` → `x <= y` | Change comparison | | |
| | Conditional | `if condition:` → `if not condition:` | Negate condition | | |
| | Statement | `return x` → `return None` | Remove statement | | |
| --- | |
| ## Why Mutation Testing? | |
| ### Problems with Traditional Coverage Metrics | |
| **Code Coverage:** | |
| ```python | |
| def is_admin(user): | |
| return True # Bug: always returns True | |
| # Test: | |
| assert is_admin(user) # 100% coverage, but bug not detected! | |
| ``` | |
| **Mutation Testing:** | |
| - Changes `return True` to `return False` | |
| - Test still passes → **Surviving mutant detected** → **Test gap found** | |
| ### Benefits | |
| 1. **Find Test Gaps**: Surviving mutants indicate missing tests | |
| 2. **Improve Test Quality**: Forces you to write better assertions | |
| 3. **Validate Edge Cases**: Mutations often test boundary conditions | |
| 4. **Measure Test Effectiveness**: More accurate than code coverage | |
| ### When to Use | |
| - **Critical Code**: Financial operations, security, data validation | |
| - **High Risk**: Authentication, authorization, payment processing | |
| - **Complex Logic**: Algorithms, state machines, workflows | |
| - **Refactoring**: Before refactoring, ensure tests are solid | |
| --- | |
| ## Quick Start | |
| ### Installation | |
| ```bash | |
| # Install mutmut | |
| pip install mutmut>=2.4.0 | |
| # Verify installation | |
| mutmut --version | |
| ``` | |
| ### Basic Usage | |
| ```bash | |
| # Run mutation tests for a single module | |
| mutmut run --paths-to-mutate core/security.py --runner "pytest tests/" | |
| # Generate HTML report | |
| mutmut html | |
| # View results | |
| mutmut results | |
| ``` | |
| ### Using the Run Scripts | |
| ```bash | |
| # Run specific target | |
| python tests/mutation_tests/scripts/run_mutation_tests.py --target priority_p0_financial | |
| # Run all targets | |
| python tests/mutation_tests/scripts/run_mutation_tests.py --all | |
| # Quick smoke test (fewer mutations) | |
| python tests/mutation_tests/scripts/run_mutation_tests.py --all --quick | |
| # Generate report | |
| python tests/mutation_tests/scripts/generate_mutation_report.py | |
| ``` | |
| --- | |
| ## Configuration | |
| ### Target Configuration (`TARGETS.ini`) | |
| ```ini | |
| [priority_p0_financial] | |
| modules = [ | |
| "core/financial_ops_engine.py", | |
| "core/financial_forensics.py" | |
| ] | |
| mutation_score_threshold = 95.0 | |
| description = "Financial operations" | |
| risk_level = "CRITICAL" | |
| ``` | |
| **Parameters:** | |
| - `modules`: List of Python files to mutate | |
| - `mutation_score_threshold`: Minimum required score (0-100) | |
| - `description`: Human-readable description | |
| - `risk_level`: CRITICAL, HIGH, MEDIUM, or LOW | |
| ### Mutmut Configuration (`mutmut.ini`) | |
| Already created in `backend/tests/mutation_tests/config/mutmut.ini` with: | |
| - Test runner command | |
| - Paths to mutate/exclude | |
| - Mutation types | |
| - Worker settings | |
| --- | |
| ## Running Mutation Tests | |
| ### Phase 5 Workflow | |
| #### 1. Initial Run | |
| ```bash | |
| # Run mutation tests for P0 (Financial & Security) | |
| python tests/mutation_tests/scripts/run_mutation_tests.py --target priority_p0_financial | |
| ``` | |
| **Expected Output:** | |
| ``` | |
| ============================================================ | |
| Running mutation tests for: priority_p0_financial | |
| Modules: core/financial_ops_engine.py, core/financial_forensics.py | |
| Score threshold: 95.0% | |
| ============================================================ | |
| - Mutation testing started ... | |
| - 127 mutants generated | |
| - 121 mutants killed | |
| - 6 mutants survived | |
| - 0 mutants timed out | |
| Mutation Score: 95.28% | |
| Threshold: 95.0% | |
| ============================================================ | |
| ✅ PASSED: Score 95.28% >= 95.0% | |
| ============================================================ | |
| ``` | |
| #### 2. Analyze Surviving Mutants | |
| If score is below threshold: | |
| ```bash | |
| # View surviving mutants | |
| mutmut results | grep SURVIVED | |
| # Apply surviving mutant for inspection | |
| mutmut apply <mutant_id> | |
| # View mutated code | |
| cat core/financial_ops_engine.py | grep -A 10 -B 10 "MUTATION" | |
| # Write test to kill mutant | |
| # Add property test, re-run | |
| ``` | |
| #### 3. Generate Report | |
| ```bash | |
| # Generate HTML report | |
| python tests/mutation_tests/scripts/generate_mutation_report.py | |
| # View report | |
| open tests/mutation_tests/reports/mutation_report.html | |
| ``` | |
| #### 4. CI/CD Integration | |
| **Weekly Workflow (`.github/workflows/mutation-tests.yml`)**: | |
| - Runs every Sunday at 3 AM UTC | |
| - Tests all priority targets | |
| - Fails if any target below threshold | |
| - Generates report artifact | |
| --- | |
| ## Interpreting Results | |
| ### Mutation Score Categories | |
| | Score | Quality | Action | | |
| |-------|---------|--------| | |
| | 95-100% | Excellent | ✅ No action needed | | |
| | 90-94% | Good | ⚠️ Review surviving mutants | | |
| | 80-89% | Fair | ⚠️ Add tests to kill survivors | | |
| | <80% | Poor | ❌ Significant test gaps | | |
| ### Surviving Mutant Analysis | |
| **Example:** | |
| ``` | |
| ⚠️ SURVIVED: core/security.py:42 (ARITHMETIC) | |
| --- | |
| Original: | |
| if attempts >= 3: | |
| block_user() | |
| Mutated: | |
| if attempts > 3: # Changed: >= to > | |
| block_user() | |
| ``` | |
| **Why Survived?** | |
| - Tests only check `attempts == 3` or `attempts > 3` | |
| - No test for `attempts == 4` (boundary condition) | |
| **Fix:** | |
| ```python | |
| # Add property test | |
| @given(attempts=st.integers(min_value=0, max_value=10)) | |
| def test_block_after_3_attempts(attempts): | |
| if attempts >= 3: | |
| assert user.is_blocked | |
| ``` | |
| ### Common Test Gaps | |
| 1. **Boundary Conditions**: `x > 3` vs `x >= 3` | |
| 2. **Null/None Handling**: Missing null checks | |
| 3. **Empty Collections**: Empty list/dict handling | |
| 4. **Off-by-One**: Loop boundaries, array indices | |
| 5. **Logic Inversions**: `and` vs `or`, `not` missing | |
| --- | |
| ## Quality Gates | |
| ### Score Thresholds by Priority | |
| | Priority | Target | Rationale | | |
| |----------|--------|-----------| | |
| | P0: Financial & Security | >95% | Highest risk (cost, security) | | |
| | P1: Core Business Logic | >90% | High risk (data integrity) | | |
| | P2: API & Tools | >85% | Medium risk (UX, functionality) | | |
| | P3: Other | >80% | Lower risk (nice-to-have) | | |
| ### CI/CD Gates | |
| **Weekly Mutation Test** (`.github/workflows/mutation-tests.yml`): | |
| ```yaml | |
| name: Mutation Tests | |
| on: | |
| schedule: | |
| - cron: '0 3 * * 0' # Sunday 3 AM UTC | |
| jobs: | |
| mutation-tests: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Run mutation tests | |
| run: | | |
| python tests/mutation_tests/scripts/run_mutation_tests.py --all | |
| - name: Upload mutation report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: mutation-report | |
| path: html/ | |
| ``` | |
| ### Failing the Build | |
| If mutation score below threshold: | |
| ```yaml | |
| - name: Check mutation score | |
| run: | | |
| SCORE=$(mutmut results | grep "Mutation score" | awk '{print $3}' | sed 's/%//') | |
| if (( $(echo "$SCORE < 95" | bc -l) )); then | |
| echo "❌ Mutation score $SCORE% below threshold 95%" | |
| exit 1 | |
| fi | |
| ``` | |
| --- | |
| ## Best Practices | |
| ### 1. Start Small | |
| Don't try to mutate everything at once. Start with: | |
| - Critical paths (authentication, payments) | |
| - High-risk code (security, financial) | |
| - Recently changed code (regression testing) | |
| ### 2. Use Property-Based Tests | |
| Property-based tests (Hypothesis) are great for killing mutants: | |
| - Generate many random inputs | |
| - Test edge cases automatically | |
| - Find boundary conditions | |
| ```python | |
| @given(x=st.integers(min_value=0, max_value=100)) | |
| def test_boundary(x): | |
| # Will test 0, 1, 2, ..., 100 | |
| # Likely to catch off-by-one mutations | |
| assert is_valid(x) | |
| ``` | |
| ### 3. Fix Surviving Mutants Immediately | |
| When you find a surviving mutant: | |
| 1. ✅ Write a regression test **before** fixing code | |
| 2. ✅ Verify test fails on mutant | |
| 3. ✅ Fix test (if test bug) or code (if code bug) | |
| 4. ✅ Verify test now kills mutant | |
| 5. ✅ Commit with detailed message | |
| ### 4. Review Test Assertions | |
| Surviving mutants often indicate weak assertions: | |
| ```python | |
| # Weak assertion (won't catch mutations) | |
| def test_add(): | |
| result = add(2, 3) | |
| assert result is not None # ❌ Too weak | |
| # Strong assertion (will catch mutations) | |
| def test_add(): | |
| result = add(2, 3) | |
| assert result == 5 # ✅ Specific assertion | |
| ``` | |
| ### 5. Balance Mutation Types | |
| Not all mutations are equally useful: | |
| - **High Value**: Arithmetic, boolean, comparison (business logic) | |
| - **Medium Value**: Conditional, statement (control flow) | |
| - **Low Value**: String literals, comments (cosmetic) | |
| Focus on high-value mutations first. | |
| ### 6. Consider Performance | |
| Mutation testing can be slow: | |
| - **1000 mutants** × **10 min test suite** = **10,000 min** ≈ **7 days** | |
| **Optimizations:** | |
| - Use parallel workers (`mutmut run --workers=auto`) | |
| - Reduce Hypothesis examples (`hypothesis_max_examples = 50`) | |
| - Test only critical paths (P0, P1) | |
| - Use incremental mutation testing (only new code) | |
| ### 7. Set Realistic Targets | |
| 100% mutation score is rarely practical: | |
| - **90-95%**: Good for critical code | |
| - **80-85%**: Acceptable for most code | |
| - **<80%**: Needs improvement | |
| Focus on critical code quality over perfect scores. | |
| --- | |
| ## Troubleshooting | |
| ### Issue: "ImportError: No module named 'mutmut'" | |
| **Solution:** | |
| ```bash | |
| pip install mutmut>=2.4.0 | |
| ``` | |
| ### Issue: "Mutations too slow" | |
| **Solution:** | |
| ```bash | |
| # Use quick mode (fewer mutations) | |
| python run_mutation_tests.py --quick | |
| # Limit number of mutants | |
| mutmut run --max-mutations 100 | |
| # Use parallel workers | |
| mutmut run --workers 4 | |
| ``` | |
| ### Issue: "Too many surviving mutants" | |
| **Solution:** | |
| 1. Run with verbose output: `mutmut run --verbose` | |
| 2. Identify patterns in surviving mutants | |
| 3. Add property-based tests for those patterns | |
| 4. Re-run mutation testing | |
| ### Issue: "Mutant causes test to hang" | |
| **Solution:** | |
| ```bash | |
| # Kill process | |
| pkill -9 mutmut | |
| # Use timeout | |
| mutmut run --timeout=30 # 30 second timeout per mutant | |
| ``` | |
| --- | |
| ## Resources | |
| - [Mutmut Documentation](https://mutmut.readthedocs.io/) | |
| - [Mutation Testing on Wikipedia](https://en.wikipedia.org/wiki/Mutation_testing) | |
| - [Property-Based Testing with Hypothesis](https://hypothesis.readthedocs.io/) | |
| - [Testing Guide](../TESTING_GUIDE.md) | |
| --- | |
| **Last Updated:** February 7, 2026 | |
| **Version:** 1.0.0 | |