|
| 1 | +# Testing Guide |
| 2 | + |
| 3 | +This guide explains how to run tests locally and in CI/CD. |
| 4 | + |
| 5 | +## Quick Start |
| 6 | + |
| 7 | +### 1. Install Test Dependencies |
| 8 | + |
| 9 | +```bash |
| 10 | +uv sync --extra test |
| 11 | +``` |
| 12 | + |
| 13 | +### 2. Start Test Database |
| 14 | + |
| 15 | +```bash |
| 16 | +# Start PostgreSQL test database in Docker |
| 17 | +./scripts/setup_test_db.sh |
| 18 | +``` |
| 19 | + |
| 20 | +This will: |
| 21 | + |
| 22 | +- Start PostgreSQL 16 in Docker on port 5433 |
| 23 | +- Create the `devbin_test` database |
| 24 | +- Wait for the database to be ready |
| 25 | + |
| 26 | +### 3. Run Tests |
| 27 | + |
| 28 | +```bash |
| 29 | +# Run all tests |
| 30 | +uv run pytest |
| 31 | + |
| 32 | +# Run only unit tests (no database required) |
| 33 | +uv run pytest tests/unit/ -v |
| 34 | + |
| 35 | +# Run with coverage report |
| 36 | +uv run pytest --cov=app --cov-report=html |
| 37 | + |
| 38 | +# Run in parallel (faster) |
| 39 | +uv run pytest -n auto |
| 40 | + |
| 41 | +# Run specific test file |
| 42 | +uv run pytest tests/unit/test_token_utils.py -v |
| 43 | +``` |
| 44 | + |
| 45 | +## Test Structure |
| 46 | + |
| 47 | +``` |
| 48 | +tests/ |
| 49 | +├── unit/ # Fast tests, no external dependencies |
| 50 | +├── integration/ # Tests with database and file system |
| 51 | +├── api/ # Full API endpoint tests |
| 52 | +└── security/ # Security-focused tests |
| 53 | +``` |
| 54 | + |
| 55 | +## Local Development |
| 56 | + |
| 57 | +### Managing Test Database |
| 58 | + |
| 59 | +```bash |
| 60 | +# Start test database |
| 61 | +docker-compose -f docker-compose.test.yml up -d |
| 62 | + |
| 63 | +# Stop test database |
| 64 | +docker-compose -f docker-compose.test.yml down |
| 65 | + |
| 66 | +# Clean up database and volumes |
| 67 | +docker-compose -f docker-compose.test.yml down -v |
| 68 | + |
| 69 | +# View logs |
| 70 | +docker-compose -f docker-compose.test.yml logs -f |
| 71 | +``` |
| 72 | + |
| 73 | +### Test Database Connection |
| 74 | + |
| 75 | +- **Host**: localhost |
| 76 | +- **Port**: 5433 (to avoid conflicts with dev database on 5432) |
| 77 | +- **Database**: devbin_test |
| 78 | +- **User**: postgres |
| 79 | +- **Password**: postgres |
| 80 | +- **Connection String**: `postgresql://postgres:postgres@localhost:5433/devbin_test` |
| 81 | + |
| 82 | +### Environment Variables |
| 83 | + |
| 84 | +Tests use environment variables from `pytest.ini` by default: |
| 85 | + |
| 86 | +```ini |
| 87 | +APP_DATABASE_URL = postgresql+asyncpg://postgres:postgres@localhost:5433/devbin_test |
| 88 | +APP_BASE_FOLDER_PATH = /tmp/devbin_test_files |
| 89 | +APP_DEBUG = true |
| 90 | +APP_ALLOW_CORS_WILDCARD = true |
| 91 | +``` |
| 92 | + |
| 93 | +You can override these by setting environment variables before running tests: |
| 94 | + |
| 95 | +```bash |
| 96 | +export APP_DATABASE_URL=postgresql+asyncpg://postgres:postgres@localhost:5432/my_test_db |
| 97 | +uv run pytest |
| 98 | +``` |
| 99 | + |
| 100 | +## CI/CD Setup |
| 101 | + |
| 102 | +### GitHub Actions |
| 103 | + |
| 104 | +The test database is automatically configured in `.github/workflows/test.yml`: |
| 105 | + |
| 106 | +```yaml |
| 107 | +services: |
| 108 | + postgres: |
| 109 | + image: postgres:16 |
| 110 | + env: |
| 111 | + POSTGRES_USER: postgres |
| 112 | + POSTGRES_PASSWORD: postgres |
| 113 | + POSTGRES_DB: devbin_test |
| 114 | + ports: |
| 115 | + - 5432:5432 |
| 116 | +``` |
| 117 | +
|
| 118 | +In CI, tests use port 5432 (the default PostgreSQL port in the service container). |
| 119 | +
|
| 120 | +### Running Tests in CI |
| 121 | +
|
| 122 | +```bash |
| 123 | +# CI sets APP_DATABASE_URL to use the service container |
| 124 | +pytest -v --cov=app --cov-report=xml |
| 125 | +coverage report --fail-under=80 |
| 126 | +``` |
| 127 | + |
| 128 | +## Test Categories |
| 129 | + |
| 130 | +### Unit Tests (Fast, Isolated) |
| 131 | + |
| 132 | +```bash |
| 133 | +pytest tests/unit/ -m unit |
| 134 | +``` |
| 135 | + |
| 136 | +- No database or file system |
| 137 | +- Mock external dependencies |
| 138 | +- < 1ms per test |
| 139 | +- Test utilities, validators, pure functions |
| 140 | + |
| 141 | +### Integration Tests |
| 142 | + |
| 143 | +```bash |
| 144 | +pytest tests/integration/ -m integration |
| 145 | +``` |
| 146 | + |
| 147 | +- Real database with transaction rollback |
| 148 | +- File system operations (temp directories) |
| 149 | +- 10-100ms per test |
| 150 | +- Test service layer |
| 151 | + |
| 152 | +### API Tests |
| 153 | + |
| 154 | +```bash |
| 155 | +pytest tests/api/ |
| 156 | +``` |
| 157 | + |
| 158 | +- Full HTTP request/response cycle |
| 159 | +- All middleware included |
| 160 | +- 50-200ms per test |
| 161 | +- Test endpoints, rate limiting, caching |
| 162 | + |
| 163 | +## Coverage Reports |
| 164 | + |
| 165 | +### View HTML Coverage Report |
| 166 | + |
| 167 | +```bash |
| 168 | +uv run pytest --cov=app --cov-report=html |
| 169 | +open htmlcov/index.html # or xdg-open on Linux |
| 170 | +``` |
| 171 | + |
| 172 | +### Coverage Targets |
| 173 | + |
| 174 | +- Overall: 80%+ (enforced in CI) |
| 175 | +- Critical modules: 90%+ |
| 176 | + - `app/services/paste_service.py` |
| 177 | + - `app/utils/token_utils.py` |
| 178 | + - `app/api/subroutes/pastes.py` |
| 179 | + |
| 180 | +## Troubleshooting |
| 181 | + |
| 182 | +### Database Connection Errors |
| 183 | + |
| 184 | +**Problem**: `connection refused` or `could not connect to server` |
| 185 | + |
| 186 | +**Solution**: |
| 187 | + |
| 188 | +1. Check if test database is running: `docker ps | grep devbin_test` |
| 189 | +2. Start database: `./scripts/setup_test_db.sh` |
| 190 | +3. Check database logs: `docker-compose -f docker-compose.test.yml logs` |
| 191 | + |
| 192 | +### Port Already in Use |
| 193 | + |
| 194 | +**Problem**: Port 5433 is already in use |
| 195 | + |
| 196 | +**Solution**: |
| 197 | + |
| 198 | +1. Change port in `docker-compose.test.yml` |
| 199 | +2. Update `pytest.ini` to match |
| 200 | +3. Restart database |
| 201 | + |
| 202 | +### Tests Fail Randomly |
| 203 | + |
| 204 | +**Problem**: Tests pass sometimes, fail other times (flaky tests) |
| 205 | + |
| 206 | +**Solution**: |
| 207 | + |
| 208 | +1. Check if tests are properly isolated (no shared state) |
| 209 | +2. Verify database cleanup between tests |
| 210 | +3. Check for timing issues (use `freezegun` for time-based tests) |
| 211 | + |
| 212 | +### Slow Tests |
| 213 | + |
| 214 | +**Problem**: Tests take too long to run |
| 215 | + |
| 216 | +**Solution**: |
| 217 | + |
| 218 | +1. Run tests in parallel: `pytest -n auto` |
| 219 | +2. Run only unit tests: `pytest tests/unit/` |
| 220 | +3. Run specific test file instead of entire suite |
| 221 | +4. Check for N+1 query issues in integration tests |
| 222 | + |
| 223 | +## Best Practices |
| 224 | + |
| 225 | +### Writing New Tests |
| 226 | + |
| 227 | +1. **Use descriptive names**: `test_create_paste_with_valid_data_returns_200` |
| 228 | +2. **Test one thing**: Each test should verify one specific behavior |
| 229 | +3. **Use fixtures**: Reuse common setup via pytest fixtures |
| 230 | +4. **Clean up**: Tests should not leave artifacts (files, DB records) |
| 231 | +5. **Mark tests**: Use `@pytest.mark.unit` or `@pytest.mark.integration` |
| 232 | + |
| 233 | +### Test Data |
| 234 | + |
| 235 | +- Use `faker` for realistic test data (IPs, user agents, names) |
| 236 | +- Use `sample_paste_data` fixture for consistent paste creation |
| 237 | +- Create factory functions for complex test objects |
| 238 | + |
| 239 | +### Mocking |
| 240 | + |
| 241 | +- **Mock external services**: Time, disk usage checks, network calls |
| 242 | +- **Use real implementations**: Database, file system (with temp dirs) |
| 243 | +- **Mock sparingly**: Real implementations catch more bugs |
| 244 | + |
| 245 | +## Continuous Integration |
| 246 | + |
| 247 | +Tests run automatically on: |
| 248 | + |
| 249 | +- Push to `master` or `develop` |
| 250 | +- Pull requests |
| 251 | + |
| 252 | +### CI Requirements |
| 253 | + |
| 254 | +- All tests must pass |
| 255 | +- Coverage must not decrease |
| 256 | +- Coverage must be >= 80% |
| 257 | +- Linting must pass (ruff) |
| 258 | + |
| 259 | +### Viewing CI Results |
| 260 | + |
| 261 | +1. Go to GitHub Actions tab |
| 262 | +2. Click on the latest workflow run |
| 263 | +3. View test results and coverage report |
0 commit comments