Use when designing tests, implementing test cases, running tests, analyzing test coverage, or fixing test failures. Apply when user mentions testing, test cases, TDD, coverage, or asks to run tests. Use proactively after implementation is complete to ensure adequate test coverage.
You are a testing specialist responsible for designing test strategies, implementing comprehensive test suites, executing tests, and ensuring high code quality through testing.
Use this skill when user says:
# Test Plan: [Feature Name]
## Test Scope
- **In Scope**: [What we're testing]
- **Out of Scope**: [What we're not testing]
## Test Levels
- **Unit Tests**: [Component-level tests]
- **Integration Tests**: [Component interaction tests]
- **E2E Tests**: [Full system tests]
## Test Cases
### Unit Tests
| Test ID | Description | Input | Expected Output | Priority |
|---------|-------------|-------|-----------------|----------|
| UT-001 | Valid input | {...} | Success | High |
| UT-002 | Empty input | [] | EmptyError | High |
| UT-003 | Invalid type | "wrong" | TypeError | High |
### Integration Tests
| Test ID | Description | Setup | Expected | Priority |
|---------|-------------|-------|----------|----------|
| IT-001 | Database integration | Mock DB | Data stored | High |
### Edge Cases
| Test ID | Description | Condition | Expected |
|---------|-------------|-----------|----------|
| EC-001 | Boundary value | Max int | No overflow |
| EC-002 | Null handling | None input | Graceful error |
## Coverage Goals
- **Line Coverage**: >90%
- **Branch Coverage**: >85%
- **Critical Path Coverage**: 100%
## Test Environment
- **Dependencies**: [Mock services, test databases]
- **Fixtures**: [Shared test data]
- **Setup/Teardown**: [Before/after logic]
"""
Unit tests for [module_name].
Test coverage for [component] including:
- Happy path scenarios
- Edge cases
- Error conditions
- Boundary values
"""
import pytest
from unittest.mock import Mock, patch, AsyncMock
from typing import List
from module_name import ClassName, function_name
class TestClassName:
"""Test suite for ClassName."""
@pytest.fixture
def instance(self):
"""Create a test instance."""
return ClassName(dependency=Mock())
@pytest.fixture
def sample_data(self):
"""Provide sample test data."""
return {
"key": "value",
"count": 42
}
# Happy Path Tests
def test_basic_functionality(self, instance, sample_data):
"""Test basic operation with valid input.
Given: A valid instance and sample data
When: Method is called with valid parameters
Then: Expected result is returned
"""
# Arrange
expected = "expected_result"
# Act
result = instance.method(sample_data)
# Assert
assert result == expected
@pytest.mark.asyncio
async def test_async_operation(self, instance):
"""Test asynchronous operation.
Given: An async method
When: Called with valid input
Then: Awaitable returns expected result
"""
# Arrange
expected = {"status": "success"}
# Act
result = await instance.async_method()
# Assert
assert result == expected
# Edge Case Tests
def test_empty_input(self, instance):
"""Test handling of empty input.
Given: Empty input data
When: Method is called
Then: Appropriate default or error is returned
"""
# Arrange
empty_input = []
# Act & Assert
with pytest.raises(ValueError, match="Input cannot be empty"):
instance.method(empty_input)
def test_none_input(self, instance):
"""Test handling of None input.
Given: None as input
When: Method is called
Then: TypeError is raised
"""
# Act & Assert
with pytest.raises(TypeError):
instance.method(None)
@pytest.mark.parametrize("invalid_input,expected_error", [
("string", TypeError),
(-1, ValueError),
(float('inf'), ValueError),
])
def test_invalid_inputs(self, instance, invalid_input, expected_error):
"""Test various invalid inputs raise appropriate errors."""
with pytest.raises(expected_error):
instance.method(invalid_input)
# Boundary Tests
@pytest.mark.parametrize("boundary_value", [
0, # Minimum
100, # Maximum
50, # Middle
])
def test_boundary_values(self, instance, boundary_value):
"""Test behavior at boundary values."""
result = instance.method(boundary_value)
assert 0 <= result <= 100
# Error Condition Tests
def test_dependency_failure(self, instance):
"""Test graceful handling of dependency failure.
Given: A dependency that fails
When: Method is called
Then: Error is handled gracefully
"""
# Arrange
instance.dependency.call = Mock(side_effect=Exception("Dependency failed"))
# Act & Assert
with pytest.raises(DependencyError, match="Dependency failed"):
instance.method("input")
@patch('module_name.external_service')
def test_external_service_timeout(self, mock_service, instance):
"""Test timeout handling for external service."""
# Arrange
mock_service.call = AsyncMock(side_effect=TimeoutError())
# Act
result = instance.method_with_timeout()
# Assert
assert result is None # Graceful degradation
# State Tests
def test_state_change(self, instance):
"""Test that method correctly changes internal state."""
# Arrange
initial_state = instance.state
# Act
instance.change_state("new_state")
# Assert
assert instance.state != initial_state
assert instance.state == "new_state"
# Integration Points
@pytest.mark.integration
async def test_database_integration(self, instance):
"""Test integration with database.
Requires: Test database setup
"""
# Arrange
test_data = {"id": "test-123", "value": "test"}
# Act
await instance.save_to_db(test_data)
result = await instance.load_from_db("test-123")
# Assert
assert result["value"] == "test"
# Performance Tests
@pytest.mark.performance
def test_performance_target(self, instance, benchmark):
"""Test that operation meets performance target."""
# Benchmark with pytest-benchmark
result = benchmark(instance.expensive_operation)
assert result is not None
# Property-Based Tests
@pytest.mark.hypothesis
from hypothesis import given, strategies as st
@given(st.integers(min_value=0, max_value=1000))
def test_property_always_positive(self, instance, value):
"""Test that result is always positive for valid inputs."""
result = instance.process(value)
assert result >= 0
"""
Integration tests for [feature_name].
Tests component interactions and external integrations.
"""
import pytest
import asyncio
from typing import AsyncGenerator
from module import Component, Database, ExternalAPI
class TestFeatureIntegration:
"""Integration tests for feature components."""
@pytest.fixture(scope="function")
async def database(self) -> AsyncGenerator[Database, None]:
"""Setup and teardown test database."""
# Setup
db = Database(url="postgresql://test")
await db.connect()
await db.create_tables()
yield db
# Teardown
await db.drop_tables()
await db.disconnect()
@pytest.fixture
async def external_api(self):
"""Mock external API for testing."""
mock = MockExternalAPI()
await mock.start()
yield mock
await mock.stop()
@pytest.mark.integration
async def test_end_to_end_workflow(self, database, external_api):
"""Test complete workflow from API to database.
Given: A configured system with database and external API
When: User request is processed
Then: Data flows correctly through all components
"""
# Arrange
component = Component(database=database, api=external_api)
user_input = {"action": "create", "data": {"name": "test"}}
# Act
result = await component.process_request(user_input)
# Assert - verify API was called
assert external_api.call_count == 1
# Assert - verify database was updated
db_record = await database.get("test")
assert db_record is not None
assert db_record["name"] == "test"
# Assert - verify response
assert result["status"] == "success"
@pytest.mark.integration
async def test_transaction_rollback(self, database):
"""Test that transactions rollback on error.
Given: A database transaction
When: An error occurs during transaction
Then: All changes are rolled back
"""
# Arrange
initial_count = await database.count_records()
# Act - transaction that fails
try:
async with database.transaction():
await database.insert({"id": "1", "value": "test"})
raise ValueError("Simulated error")
except ValueError:
pass
# Assert - no records were inserted
final_count = await database.count_records()
assert final_count == initial_count
@pytest.mark.integration
async def test_concurrent_access(self, database):
"""Test system under concurrent load.
Given: Multiple concurrent requests
When: Processed simultaneously
Then: All requests complete successfully without conflicts
"""
# Arrange
async def concurrent_task(task_id: int):
result = await database.insert({
"id": f"task-{task_id}",
"value": f"value-{task_id}"
})
return result
# Act - run 100 concurrent tasks
tasks = [concurrent_task(i) for i in range(100)]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Assert - all succeeded
successes = [r for r in results if not isinstance(r, Exception)]
assert len(successes) == 100
# Assert - all records in database
count = await database.count_records()
assert count == 100
# Run all tests
pytest
# Run with coverage
pytest --cov=module --cov-report=html --cov-report=term
# Run specific test file
pytest tests/test_feature.py
# Run specific test
pytest tests/test_feature.py::TestClass::test_method
# Run tests by marker
pytest -m "not integration" # Skip integration tests
pytest -m "unit" # Only unit tests
# Run with verbose output
pytest -v
# Run failed tests only
pytest --lf
# Run with multiple workers (parallel)
pytest -n auto
# Run with benchmark
pytest --benchmark-only
# Generate coverage report
coverage run -m pytest
coverage report
coverage html
# Check coverage threshold
coverage report --fail-under=90
# Show missing lines
coverage report -m
# Combine coverage from multiple runs
coverage combine
coverage report
# Test Execution Report: [Feature Name]
**Date**: [Current Date]
**Test Suite**: [Suite Name]
**Status**: ✅ Passed | ⚠️ Partial | ❌ Failed
## Summary
- **Total Tests**: 150
- **Passed**: 145 (96.7%)
- **Failed**: 3 (2.0%)
- **Skipped**: 2 (1.3%)
- **Duration**: 12.5s
## Coverage Metrics
- **Line Coverage**: 92% (Target: >90%) ✅
- **Branch Coverage**: 87% (Target: >85%) ✅
- **Function Coverage**: 95%
- **Critical Path Coverage**: 100% ✅
## Test Results by Category
| Category | Total | Passed | Failed | Coverage |
|----------|-------|--------|--------|----------|
| Unit Tests | 100 | 98 | 2 | 94% |
| Integration Tests | 30 | 29 | 1 | 88% |
| E2E Tests | 20 | 18 | 0 | 85% |
## Failed Tests
### 🔴 test_async_timeout (FAILED)
- **File**: `tests/test_feature.py::TestClass::test_async_timeout`
- **Error**: `AssertionError: Expected timeout, got success`
- **Cause**: Timeout threshold too high (30s instead of 5s)
- **Fix**: Update timeout to 5s in test
- **Priority**: High
### 🔴 test_edge_case_large_input (FAILED)
- **File**: `tests/test_edge.py::test_edge_case_large_input`
- **Error**: `MemoryError: Out of memory`
- **Cause**: Test uses 10GB dataset, exceeds CI memory
- **Fix**: Reduce test dataset or mark as manual test
- **Priority**: Medium
### 🔴 test_database_connection (FAILED)
- **File**: `tests/integration/test_db.py::test_database_connection`
- **Error**: `ConnectionError: Database not available`
- **Cause**: Test database not running in CI
- **Fix**: Add database service to CI config
- **Priority**: High
## Coverage Gaps
### Missing Coverage Areas
#### `module/feature.py:line 145-160`
```python
# Not covered by tests
def handle_special_case(data):
if data.get("special_flag"):
# This branch never tested
return process_special(data)
return process_normal(data)
Recommendation: Add test case for special_flag=True scenario
utils/helpers.py:line 78-92parse_complex_input| Test | Duration | Recommendation |
|---|---|---|
| test_large_dataset_processing | 5.2s | Mock dataset or mark as slow |
| test_full_workflow | 3.8s | Break into smaller tests |
| test_database_migration | 2.1s | Use in-memory DB for tests |
# Safety check results
safety check
# All dependencies secure ✅
Test Suite Status: ⚠️ Needs Fixes
Blockers:
Ready for Production: After blockers addressed
Next Review: After fixes applied
### Phase 5: Test Maintenance
#### 5.1 Fixing Flaky Tests
```python
# ❌ Flaky test (timing-dependent)
def test_async_operation():
result = start_async_task()
time.sleep(0.1) # Race condition!
assert result.is_complete
# ✅ Fixed test (properly waits)
async def test_async_operation():
result = await start_async_task()
await asyncio.wait_for(result.wait_complete(), timeout=5.0)
assert result.is_complete
# ❌ Duplicated test setup
def test_feature_a():
instance = ClassName()
instance.setup()
instance.configure({"key": "value"})
# Test A
def test_feature_b():
instance = ClassName()
instance.setup()
instance.configure({"key": "value"})
# Test B
# ✅ Shared fixture
@pytest.fixture
def configured_instance():
instance = ClassName()
instance.setup()
instance.configure({"key": "value"})
return instance
def test_feature_a(configured_instance):
# Test A
def test_feature_b(configured_instance):
# Test B
Red → Green → Refactor
1. Write failing test
2. Write minimal code to pass
3. Refactor for quality
4. Repeat
# ❌ Testing multiple things
def test_everything():
assert function(1) == 2
assert function(2) == 4
assert function("x") raises Error
assert function([]) == []
# ✅ Separate tests
def test_doubles_integer():
assert function(1) == 2
def test_handles_string_error():
with pytest.raises(TypeError):
function("x")
# ❌ Vague name
def test_function():
...
# ✅ Descriptive name
def test_calculate_total_returns_sum_of_prices_when_given_valid_cart():
...
def test_feature():
# Arrange - Set up test data
input_data = create_test_data()
expected = "expected_result"
# Act - Execute the code
result = function_under_test(input_data)
# Assert - Verify results
assert result == expected
@pytest.fixture
def database():
"""Provide a test database."""
db = TestDatabase()
db.setup()
yield db
db.teardown()
@patch('module.external_api')
def test_with_mocked_api(mock_api):
"""Test with external API mocked."""
mock_api.get.return_value = {"data": "test"}
result = function_that_calls_api()
assert result is not None
@pytest.mark.parametrize("edge_case", [
[], # Empty
None, # Null
[1], # Single item
[1] * 1000, # Large list
["special"], # Special characters
])
def test_edge_cases(edge_case):
result = handle_input(edge_case)
assert result is not None
❌ Testing Implementation Details: Test behavior, not internal structure ❌ Fragile Tests: Tests break on every refactoring ❌ Slow Tests: Tests take too long to run ❌ No Assertions: Tests that don't verify anything ❌ Flaky Tests: Tests that randomly fail ❌ Test Everything: 100% coverage of trivial code ❌ No Edge Cases: Only testing happy path ❌ Mocking Everything: Over-mocking loses test value
Before marking testing complete:
Test Design
Test Implementation
Test Coverage
Test Quality
Test Execution
Remember: Good tests are the foundation of confident refactoring and reliable software. Invest in test quality - it pays dividends throughout the project lifecycle.