| name | testing-workflow |
| description | Testing workflow patterns and quality standards. Activate when working with tests, test files, test directories, code quality tools, coverage reports, or testing tasks. Includes zero-warnings policy, targeted testing during development, mocking patterns, and best practices across languages. |
| location | user |
Testing Workflow
Testing workflow patterns and quality standards for various frameworks and languages.
CRITICAL: Zero Warnings Tolerance
Treat all warnings as errors. No exceptions.
| Status | Output | Action |
|---|---|---|
| ✅ PASS | All tests passed, no warnings | Proceed |
| ❌ FAIL | Tests passed with DeprecationWarning | Fix immediately |
| ❌ FAIL | Any warning present | Block commit |
Pre-Commit Requirements:
- ✅ All tests pass
- ✅ Zero warnings
- ✅ No linting errors
- ✅ No type errors
- ✅ Code formatted
Never commit with:
- ❌ Failing tests
- ❌ Any warnings
- ❌ Linting errors
- ❌ Unformatted code
Testing Strategy
Test Pyramid
- Unit Tests (70%) - Fast, isolated, test individual functions/classes
- Integration Tests (20%) - Test component interactions
- End-to-End Tests (10%) - Full system tests
What to Test
✅ DO test:
- Public APIs and interfaces
- Business logic and calculations
- Edge cases (empty inputs, None values, boundaries)
- Error handling and exceptions
- Data validation
- Critical paths through the application
❌ DON'T test:
- Private implementation details
- Third-party library internals
- Trivial getters/setters
- Framework magic (unless you suspect bugs)
Development Workflow
During Development:
- Run targeted tests for fast iteration
- Fix issues immediately
Before Commit:
- Run full suite
- Fix all warnings/errors
- May run 1500+ tests
Test Organization
Directory Structure
tests/
├── __init__.py / conftest.py # Shared fixtures and setup
├── unit/ # Fast, isolated tests
│ └── test_*.py
├── integration/ # Component interaction tests
│ └── test_*.py
└── e2e/ # End-to-end tests
└── test_*.py
Naming Conventions
- Test files:
test_*.pyor*_test.py - Test classes:
Test*(e.g.,TestUserService) - Test functions:
test_*(e.g.,test_create_user_success) - Fixtures: Descriptive names (e.g.,
user_service,mock_database)
CRITICAL: Never name non-test classes with "Test" prefix - framework will try to collect them as tests.
Coverage Requirements
Coverage Goals
- Minimum: 80% overall coverage
- Critical paths: 100% coverage
- New code: Should not decrease overall coverage
- Focus: Behavior over line count
What to Cover
Test these:
- Business logic, algorithms
- Edge cases, boundary conditions
- Error handling and exceptions
- Integration points, APIs
- Data validation
- Security-critical paths
Skip these:
- Trivial getters/setters
- Private implementation details
- Third-party library internals
- Simple data classes
- Framework magic (unless suspected bug)
Edge Cases to Test
- Empty inputs (empty strings, empty collections)
- None/null values
- Boundary values (zero, maximum, minimum)
- Single item cases
- Negative numbers
- Large numbers
- Mixed positive and negative values
Test Structure - Arrange-Act-Assert Pattern
All tests follow the Arrange-Act-Assert (AAA) pattern for clarity:
- Arrange - Set up test data and conditions
- Act - Execute the functionality being tested
- Assert - Verify the results
This structure makes tests:
- Easy to understand at a glance
- Simple to maintain
- Consistent across the codebase
Python Example
# ✅ GOOD - Clear AAA structure
def test_user_registration():
# Arrange
user_data = {"email": "test@example.com", "password": "secure"}
# Act
result = register_user(user_data)
# Assert
assert result.success
assert result.user.email == "test@example.com"
# ❌ BAD - Testing implementation details
def test_internal_method():
obj = MyClass()
assert obj._internal_state == expected # Don't test private state
Fixtures
# conftest.py
@pytest.fixture
def db_connection():
conn = create_test_database()
yield conn
conn.close()
@pytest.fixture
def sample_user():
return User(email="test@example.com", name="Test User")
# test_file.py
def test_save_user(db_connection, sample_user):
save_user(db_connection, sample_user)
assert user_exists(db_connection, sample_user.email)
Scopes: function (default), class, module, session
Parametrized Tests
@pytest.mark.parametrize("input,expected", [
("hello", "HELLO"),
("world", "WORLD"),
("", ""),
(None, None),
])
def test_uppercase_conversion(input, expected):
assert to_uppercase(input) == expected
Mocking
from unittest.mock import Mock, patch
# Mock external API
@patch('module.requests.get')
def test_api_call(mock_get):
mock_get.return_value.json.return_value = {"status": "ok"}
result = fetch_data()
assert result["status"] == "ok"
# Dependency injection for testability
class UserService:
def __init__(self, db_connection):
self.db = db_connection
def test_get_user():
mock_db = Mock()
mock_db.query.return_value = {"id": 1, "name": "Test"}
service = UserService(mock_db)
assert service.get_user(1)["name"] == "Test"
Testing Exception Handling
Tests should verify that exceptions are raised with correct messages for invalid inputs:
# ✅ GOOD - Testing exception
def test_create_user_invalid_email(user_service):
"""Test user creation fails with invalid email."""
user_data = {
"username": "testuser",
"email": "invalid-email", # Invalid format
"age": 25
}
# Expect exception when invalid email is provided
with pytest.raises(ValidationError) as exc_info:
user_service.create_user(user_data)
assert "email" in str(exc_info.value)
Mocking External Dependencies
Tests should mock external dependencies to:
- Isolate the code being tested
- Avoid real external calls (APIs, databases, network)
- Control behavior for edge cases
- Speed up test execution
from unittest.mock import Mock, patch
# Mock external service
@patch('module.requests.get')
def test_api_call(mock_get):
mock_get.return_value.json.return_value = {"status": "ok"}
result = fetch_data()
# Verify mock was called and behavior is correct
mock_get.assert_called_once()
assert result["status"] == "ok"
# Dependency injection for testability
class UserService:
def __init__(self, db_connection):
self.db = db_connection
def test_get_user():
mock_db = Mock()
mock_db.query.return_value = {"id": 1, "name": "Test"}
service = UserService(mock_db)
assert service.get_user(1)["name"] == "Test"
Integration Testing
Integration tests verify that multiple components work together correctly. They typically:
- Use real or test databases/services
- Test API endpoints with real infrastructure
- Verify component interactions
- Run slower than unit tests
@pytest.fixture(scope="module")
def test_database():
"""Provide test database for integration tests."""
db = create_test_database()
run_migrations(db)
yield db
cleanup_database(db)
def test_user_operations(test_database):
"""Test user repository with real database."""
user = create_user(test_database, email="test@example.com")
assert user.id is not None
retrieved = get_user(test_database, user.id)
assert retrieved.email == "test@example.com"
TDD Pattern Example
# 1. Write failing test
def test_calculate_discount():
result = calculate_discount(price=100, discount_percent=10)
assert result == 90
# 2. Implement
def calculate_discount(price, discount_percent):
if not 0 <= discount_percent <= 100:
raise ValueError("Discount must be between 0 and 100")
return price - (price * discount_percent / 100)
Test Configuration
[tool.pytest.ini_options]
testpaths = ["tests"]
python_files = ["test_*.py", "*_test.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
addopts = [
"-v",
"--strict-markers",
"--tb=short",
"--cov=app",
"--cov-report=term-missing",
]
markers = [
"slow: marks tests as slow (deselect with '-m \"not slow\"')",
"integration: marks tests as integration tests",
]
Test Markers and Categorization
Tests can be categorized with markers to allow selective execution:
Common marker categories:
slow- Tests that take longer to run (deselect with-m "not slow")integration- Integration tests that use external servicesunit- Unit tests (fast, isolated)e2e- End-to-end testsperformance- Performance/benchmark tests
@pytest.mark.slow
def test_expensive_operation():
result = process_large_dataset()
assert result.success
@pytest.mark.integration
def test_database_integration():
result = query_database()
assert result is not None
# Run only fast tests
pytest -m "not slow"
# Run only integration tests
pytest -m integration
Performance Testing
Performance tests verify that operations complete within acceptable time limits:
import time
def test_performance_within_limit(data_processor):
"""Test processing completes within time limit."""
large_dataset = generate_test_data(10000)
start = time.time()
result = data_processor.process(large_dataset)
duration = time.time() - start
assert duration < 1.0 # Should complete in under 1 second
assert len(result) == 10000
Development Workflow
- Write/modify code
- Run targeted tests
- Fix issues immediately
- Run full suite (
make check) - Commit when zero warnings
Always:
- ✅ Test after every change
- ✅ Fix warnings immediately
- ✅ Add tests for new features
Never:
- ❌ Commit with failures/warnings
- ❌ Skip tests after changes
- ❌ Ignore failures as "known issues"
Essential Commands
# Development - Targeted
pytest tests/unit/test_file.py -v # Specific file
pytest -k "test_name" -v # Pattern match
pytest tests/unit/test_file.py::test_func # Exact test
pytest -v --tb=short # Cleaner errors
# Debugging
pytest -l # Show locals
pytest --pdb # Debug on failure
pytest -x # Stop on first failure
pytest --lf # Rerun last failed
# Coverage
pytest --cov=app --cov-report=html # HTML report
pytest --cov=app --cov-report=term-missing # Terminal report
# Verification
make check # Full suite + quality
uv run pytest # All tests
uv run black --check app/ tests/ # Format check
uv run isort --check app/ tests/ # Import order
uv run flake8 app/ tests/ # Linting
uv run mypy app/ tests/ # Type check
Test Quality Checklist
- ✅ Run in isolation (no shared state)
- ✅ Deterministic (same result every time)
- ✅ Fast (mock slow operations)
- ✅ Clear names document behavior
- ✅ Test edge cases and errors
- ✅ Zero warnings in output
- ✅ >80% coverage on critical paths
TL;DR: Zero warnings policy. Follow test pyramid. Arrange-Act-Assert pattern. Mock external dependencies. Test behavior not implementation. >80% coverage on critical paths. Run targeted tests during development, full suite before commit.