Python Testing Skill
Testing Framework & Configuration
- Use
pytestcombined withpytest-asynciofor all testing. - Auto Mode: The project is configured with
asyncio_mode = "auto"inserver/pyproject.toml. Because of this, any test function declared withasync defis automatically run inside an asyncio event loop. - No Decorators Needed: Do not use the
@pytest.mark.anyioor@pytest.mark.asynciodecorators on async test functions, asasyncio_mode = "auto"handles this automatically.
Directory Structure
All test files are located under server/tests/ and are organized as follows:
tests/unit/: Contains unit tests testing components in isolation. Subdirectories match the application structure (e.g.,core,repositories,service,utils).tests/integrations/: Contains end-to-end integration tests that call API endpoints using a test client.tests/mocks/: Contains mock classes for repositories and external integrations (e.g.MockTodoRepository).tests/factories.py: Contains factory methods (likecreate_user,create_todo) to generate mock data.tests/conftest.py: Contains common fixtures and configuration for all tests.
Conventions & Best Practices
- Docstrings: Every test function MUST have a descriptive docstring explaining exactly what it verifies.
- Mocking: Mock external dependencies (like database repositories or third-party HTTP clients) when writing unit tests for services. Use the classes in
tests/mocks/. - Fixture Scopes: Ensure all fixtures are explicitly scoped (e.g.
scope="session"). - Code Coverage: Ensure test coverage is strictly more than 80%.
- Async loops: Use the default loop scopes defined in
pyproject.toml(asyncio_default_fixture_loop_scope = "session"andasyncio_default_test_loop_scope = "session"). - No need to add comments inside any of the test code. Docstrings are enough.
Example: Unit Test
async def test_add_todo_without_attachments(
todo_service: TodoService,
mock_todo_repository: MockTodoRepository,
) -> None:
"""Test that creating a Todo task without specifying attachment IDs persists the Todo correctly."""
todo_data = TodoCreate(
title="Buy groceries",
description="Milk and bread",
is_completed=False,
attachment_ids=None,
)
todo = await todo_service.add(todo_data)
assert len(mock_todo_repository.todos_db) == 1
assert mock_todo_repository.todos_db[0].title == "Buy groceries"
assert mock_todo_repository.todos_db[0].id == todo.id
Example: Integration Test
async def test_get_todos_list_success(client: Any) -> None:
"""Integration test verifying that an authenticated user can list their seeded Todo items successfully."""
api_client = await client("user@email.com", "SecurePass123!")
response = await api_client.get("/v2/todos/")
assert response.status_code == 200
data = response.json()
assert "founds" in data