Files
TimeTracker/docs/TESTING_COVERAGE_GUIDE.md
T
Dries Peeters 113a57d2eb testing updates
2025-10-10 11:37:23 +02:00

322 lines
7.9 KiB
Markdown

# Testing Coverage Guide
## Understanding the Coverage Issue
### The Problem
When running route tests with coverage requirements:
```bash
pytest -m routes --cov=app --cov-fail-under=50
```
You may see:
```
FAIL Required test coverage of 50% not reached. Total coverage: 27.81%
```
### Why This Happens
The issue occurs because:
1. **Route tests only exercise route handlers** - They test the endpoints in `app/routes/`
2. **Coverage measures the entire `app` module** - Including models, utils, config, etc.
3. **Most code isn't executed by routes alone** - Models, utilities, and business logic require comprehensive testing across all test types
This is **conceptually correct behavior**. Route tests shouldn't execute 50% of your entire codebase - they should test routes. Other code is tested by model tests, integration tests, etc.
## Solutions
### Option 1: Run Tests Without Marker-Specific Coverage (Recommended)
**For development and debugging specific test categories:**
```bash
# Run route tests without coverage requirements
make test-routes
# Or directly:
pytest -m routes -v
```
**For comprehensive coverage analysis:**
```bash
# Run ALL tests with coverage requirement
make test-coverage
# Or directly:
pytest --cov=app --cov-report=html --cov-report=term-missing --cov-fail-under=50
```
### Option 2: Measure Coverage Only for Routes
If you specifically want to measure route coverage:
```bash
# Measure coverage only for the routes module
pytest -m routes --cov=app/routes --cov-report=term-missing
```
This will show you what percentage of your routes are tested, not the entire app.
### Option 3: Run Coverage on All Tests Together
The standard approach in most projects:
```bash
# Run all tests together with coverage
pytest --cov=app --cov-report=html --cov-report=term-missing --cov-fail-under=50
```
This gives you the true coverage across your entire test suite.
## Test Organization Strategy
### Test Markers
The project uses pytest markers to organize tests:
- `@pytest.mark.smoke` - Critical functionality (health checks, basic routes)
- `@pytest.mark.unit` - Unit tests (isolated, fast)
- `@pytest.mark.integration` - Integration tests (multiple components)
- `@pytest.mark.routes` - Route/endpoint tests
- `@pytest.mark.api` - API endpoint tests
- `@pytest.mark.models` - Model tests
- `@pytest.mark.database` - Database tests
- `@pytest.mark.security` - Security tests
### Running Different Test Suites
```bash
# Quick smoke test (fastest, for CI)
make test-smoke
# Unit tests only
make test-unit
# Integration tests
make test-integration
# Route tests (no coverage requirement)
make test-routes
# Model tests
make test-models
# API tests
make test-api
# Full test suite with 50% coverage requirement
make test-coverage
# Generate coverage report without failing on threshold
make test-coverage-report
```
## Coverage Targets by Test Type
Different test types have different coverage expectations:
| Test Type | Expected Coverage | Scope |
|-----------|------------------|-------|
| Smoke tests | Low (~10-20%) | Critical paths only |
| Route tests | Low (~20-30%) | Routes + directly called utilities |
| Model tests | Medium (~30-40%) | Models + database operations |
| Unit tests | Medium (~40-60%) | Specific modules being tested |
| Integration tests | High (~60-80%) | Multiple components together |
| **Full suite** | **High (50%+)** | **Entire codebase** |
## Best Practices
### 1. Don't Enforce Coverage on Marker-Specific Tests
**Wrong:**
```bash
pytest -m routes --cov=app --cov-fail-under=50
```
**Correct:**
```bash
# For debugging/development
pytest -m routes -v
# For coverage analysis
pytest --cov=app --cov-fail-under=50
```
### 2. Use Coverage to Find Gaps, Not as a Goal
Coverage percentage is a tool to find untested code, not a target to hit. Focus on:
- Testing critical functionality
- Testing edge cases
- Testing error conditions
- Testing user workflows
### 3. Combine Test Types for Complete Coverage
```bash
# This is how to get meaningful coverage
pytest tests/ --cov=app --cov-report=html --cov-fail-under=50
```
Individual test types complement each other:
- **Route tests**: Ensure endpoints work
- **Model tests**: Ensure data integrity
- **Integration tests**: Ensure components work together
- **Unit tests**: Ensure individual functions work
### 4. Review Coverage Reports
After running tests with coverage:
```bash
# Generate HTML report
pytest --cov=app --cov-report=html
# Open the report
# The report is in htmlcov/index.html
```
Look for:
- Untested critical paths
- Error handling code
- Edge cases
- Business logic
## CI/CD Coverage Strategy
### Development (develop branch)
- Runs smoke tests only (fast feedback)
- No coverage requirements
- Focus on catching obvious breaks
### Pull Requests
- Runs full test suite
- No strict coverage requirement yet
- Migration validation for model changes
### Release (main/master branch)
- Runs full test suite
- Enforces 50% coverage requirement
- Security audit
- Integration tests
## Current Test Coverage
To see current coverage:
```bash
# Run tests with coverage
make test-coverage-report
# View HTML report
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # Linux
start htmlcov/index.html # Windows
```
## Adding More Route Tests
If you want to improve route test coverage, add tests for:
### Missing Route Tests
- Task routes (`/tasks/*`)
- Comment routes (`/comments/*`)
- More comprehensive API tests
- Form submission tests
- File upload tests
- Pagination tests
### Example: Adding Task Route Tests
```python
# tests/test_routes.py
@pytest.mark.integration
@pytest.mark.routes
def test_tasks_list_page(authenticated_client):
"""Test tasks list page."""
response = authenticated_client.get('/tasks')
assert response.status_code == 200
@pytest.mark.integration
@pytest.mark.routes
def test_task_create_page(authenticated_client, project):
"""Test task creation page."""
response = authenticated_client.get(f'/tasks/new?project_id={project.id}')
assert response.status_code == 200
@pytest.mark.integration
@pytest.mark.routes
@pytest.mark.api
def test_create_task_api(authenticated_client, project, user, app):
"""Test creating a task via API."""
with app.app_context():
response = authenticated_client.post('/api/tasks', json={
'name': 'Test Task',
'project_id': project.id,
'description': 'Test task description',
'priority': 'medium'
})
assert response.status_code in [200, 201]
```
## Troubleshooting
### "pytest: error: unrecognized arguments: --cov=app"
This means `pytest-cov` is not installed:
```bash
pip install -r requirements-test.txt
```
### Coverage too low even with all tests
This is normal if you have:
- Large utility modules
- Configuration code
- Error handling code
- Admin-only features
- Legacy code
Focus on:
1. Test critical user paths
2. Test error conditions
3. Test business logic
4. Don't worry about 100% coverage
### Tests pass individually but fail in suite
This usually indicates:
- Test pollution (tests affecting each other)
- Shared state issues
- Database not being cleaned between tests
Fix by using proper fixtures and isolation.
## Summary
**Do:**
- Run full test suite for coverage analysis
- Use markers to organize and run specific test types
- Focus on testing critical functionality
- Use coverage reports to find gaps
**Don't:**
- Enforce coverage thresholds on marker-specific tests
- Chase 100% coverage
- Write tests just to increase coverage percentage
- Mix coverage analysis with debugging/development testing
## Quick Reference
```bash
# Development workflow
make test-routes # Debug route tests
make test-models # Debug model tests
make test-unit # Debug unit tests
# CI/CD workflow
make test-smoke # Quick validation
make test-coverage # Full coverage with 50% threshold
# Coverage analysis
make test-coverage-report # Generate report without failing
open htmlcov/index.html # Review coverage
```