Test-Driven Development follows the Red-Green-Refactor cycle:
- Red: Write failing test
- Green: Write minimal code to pass test
- Refactor: Improve code while keeping tests passing
Test individual functions in isolation:
@pytest.mark.unit
def test_validate_record_success() -> None:
"""Test validating valid record."""
data = {"id": 1, "value": 10.5, "label": "test"}
record = DataValidator.validate_record(data)
assert record.id == 1Test multiple components working together:
@pytest.mark.integration
def test_end_to_end_pipeline(temp_dir: Path) -> None:
"""Test complete pipeline flow."""
# Setup
input_file = temp_dir / "input.csv"
# Execute
pipeline = DataPipeline()
result = pipeline.execute(input_file)
# Assert
assert result.success is Truedef test_feature() -> None:
# Arrange: Setup test data
df = pd.DataFrame({"value": [1, 2, 3]})
# Act: Execute function
result = process(df)
# Assert: Verify results
assert len(result) == 3@pytest.fixture
def sample_data() -> pd.DataFrame:
"""Create sample data."""
return pd.DataFrame({"id": [1, 2, 3]})
def test_with_fixture(sample_data: pd.DataFrame) -> None:
"""Test using fixture."""
assert len(sample_data) == 3@pytest.mark.parametrize("input,expected", [
(1, 2),
(2, 4),
(3, 6),
])
def test_multiply(input: int, expected: int) -> None:
"""Test multiplication."""
assert input * 2 == expected# All tests
pytest
# With coverage report
pytest --cov=src/ai_project --cov-report=html
# Specific test file
pytest tests/unit/test_config.py
# Specific test
pytest tests/unit/test_config.py::TestSettings::test_default_settings
# Unit tests only
pytest -m unit
# Integration tests only
pytest -m integration
# Verbose output
pytest -v
# Stop on first failure
pytest -x
# Show print statements
pytest -s- Minimum 80% overall coverage
- All public functions must be tested
- Critical paths must have 100% coverage
# Generate coverage report
pytest --cov=src/ai_project --cov-report=html
# View report
open htmlcov/index.htmlUse pytest-mock for mocking:
def test_with_mock(mocker):
"""Test with mocked dependency."""
mock_loader = mocker.patch("ai_project.processors.loader.DataLoader.load_csv")
mock_loader.return_value = pd.DataFrame({"id": [1, 2, 3]})
result = mock_loader("dummy.csv")
assert len(result) == 3@pytest.mark.asyncio
async def test_async_function() -> None:
"""Test async function."""
result = await async_process()
assert result is not None- Test one thing per test - Single responsibility
- Use descriptive names -
test_validate_record_with_negative_value_fails - Keep tests fast - Mock external dependencies
- Test edge cases - Empty data, nulls, invalid input
- Avoid test interdependence - Each test should be independent
- Use fixtures for setup - DRY principle
- Test error paths - Verify exceptions are raised correctly
Tests run automatically on:
- Pull requests
- Commits to main branch
- Scheduled daily runs
See .github/workflows/ for CI configuration.