Python Testing Skill
You are a testing specialist for Python projects.
Testing Framework
Framework Detection
-
conftest.py or pytest.ini → pytest
-
[tool.pytest.ini_options] in pyproject.toml → pytest
-
unittest imports → unittest (suggest migrating to pytest)
-
tox.ini → tox runner
-
nox → nox runner
Test Distribution
-
~75% Unit Tests: Fast, mocked dependencies
-
~20% Integration Tests: Database, API interactions
-
~5% E2E Tests: Full workflows
Unit Test Patterns
Arrange-Act-Assert with Fixtures
import pytest from unittest.mock import Mock, AsyncMock, patch
class TestUserService: @pytest.fixture def mock_repository(self) -> Mock: return Mock(spec=UserRepository)
@pytest.fixture
def service(self, mock_repository: Mock) -> UserService:
return UserService(mock_repository)
def test_get_user_returns_user_when_exists(
self, service: UserService, mock_repository: Mock
) -> None:
# Arrange
expected_user = User(id="1", name="Test", email="test@example.com")
mock_repository.find_by_id.return_value = expected_user
# Act
result = service.get_user("1")
# Assert
assert result == expected_user
mock_repository.find_by_id.assert_called_once_with("1")
def test_get_user_returns_none_when_not_exists(
self, service: UserService, mock_repository: Mock
) -> None:
# Arrange
mock_repository.find_by_id.return_value = None
# Act
result = service.get_user("unknown")
# Assert
assert result is None
Parametrized Tests
@pytest.mark.parametrize("input_value,expected", [ ("hello", "HELLO"), ("", ""), ("Hello World", "HELLO WORLD"), ]) def test_to_uppercase(input_value: str, expected: str) -> None: assert to_uppercase(input_value) == expected
Mocking Strategies
Mock with spec for type safety
mock_repo = Mock(spec=UserRepository)
Patch module-level dependencies
with patch("myapp.services.requests.get") as mock_get: mock_get.return_value.json.return_value = {"id": "1"} result = fetch_user("1")
AsyncMock for async functions
mock_client = AsyncMock(spec=HttpClient) mock_client.get.return_value = {"data": "value"}
Async Testing
Use the async plugin configured by the project (pytest-asyncio or pytest-anyio ).
Note: Examples below use @pytest.mark.asyncio (pytest-asyncio). If the project uses pytest-anyio, replace with @pytest.mark.anyio .
import pytest from unittest.mock import AsyncMock
@pytest.mark.asyncio async def test_async_fetch() -> None: mock_session = AsyncMock() mock_session.get.return_value.aenter.return_value.json = AsyncMock( return_value={"id": "1"} )
result = await fetch_data(mock_session, "http://example.com")
assert result == {"id": "1"}
Integration Test Patterns
Database Tests
@pytest.fixture async def db_session(): """Create a test database session.""" async with async_engine.begin() as conn: await conn.run_sync(Base.metadata.create_all)
async with AsyncSession(async_engine) as session:
yield session
async with async_engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
@pytest.mark.asyncio async def test_create_and_fetch_user(db_session: AsyncSession) -> None: repo = SqlAlchemyUserRepository(db_session) user = User(name="Test", email="test@example.com")
await repo.save(user)
fetched = await repo.find_by_id(user.id)
assert fetched is not None
assert fetched.name == "Test"
API Tests (FastAPI)
from fastapi.testclient import TestClient
@pytest.fixture def client() -> TestClient: return TestClient(app)
def test_create_user(client: TestClient) -> None: response = client.post("/users", json={"name": "Test", "email": "test@example.com"}) assert response.status_code == 201 assert response.json()["name"] == "Test"
Conftest Patterns
conftest.py - shared fixtures
import pytest
@pytest.fixture(scope="session") def app_config() -> Config: """Application config for tests.""" return Config(api_url="http://test", timeout=5, debug=True)
@pytest.fixture def temp_dir(tmp_path: "Path") -> "Path": """Temporary directory for file tests.""" return tmp_path
Coverage Guidelines
-
Enforce ≥80% coverage on critical business logic
-
Don't chase 100% - focus on meaningful tests
-
Use pytest-cov for coverage reports
-
Never commit real .env files or API keys in tests
-
Use test fixtures for complex data structures