Skip to main content

python-testing

Python-specific testing practices with pytest, fixtures, mocking, async testing, coverage configuration, and uv execution rules. Activate when working with pytest files, conftest.py, test directories, pyproject.toml testing configuration, or Python test-related tasks.

Source facts

Repository
ilude/claude-code-config
Last source activity
November 17, 2025 at 23:00
Detected SKILL.md language
English
Stars
8
Forks
0

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
python-testing
description
Python-specific testing practices with pytest, fixtures, mocking, async testing, coverage configuration, and uv execution rules. Activate when working with pytest files, conftest.py, test directories, pyproject.toml testing configuration, or Python test-related tasks.
# Python Testing Practices Python-specific testing patterns and best practices using pytest, complementing general testing-workflow skill. ## CRITICAL: UV Execution Rules **NEVER use `-m` flag with uv run:** ```bash # ✅ CORRECT - UV pytest execution uv run pytest uv run pytest -v uv run pytest tests/unit/ -v uv run pytest --cov=app --cov-report=html # ❌ WRONG - Never add -m flag # ❌ uv run -m pytest # ❌ uv run -m pytest -v ``` **Always use `uv run pytest` directly** (never call pytest directly in uv projects). --- ## Pytest Fixtures ### Fixture Scopes Pytest fixtures support different scopes for setup/teardown control: ```python @pytest.fixture(scope="session") def database_connection(): """Created once per test session - expensive setup.""" connection = setup_expensive_database() yield connection connection.cleanup() @pytest.fixture(scope="module") def api_client(): """Created once per test module.""" return APIClient(config="test") @pytest.fixture(scope="class") def service_instance(): """Created once per test class.""" return Service() @pytest.fixture(scope="function") # Default def user(): """Created for each test function - isolated.""" return User(email="test@example.com") ``` ### Fixture Setup and Teardown Use `yield` pattern for setup/teardown with proper cleanup: ```python @pytest.fixture def database(): """Fixture with setup and teardown.""" # Setup db = Database(":memory:") db.initialize() db.create_schema() # Provide to test yield db # Teardown db.close() @pytest.fixture def mock_file(tmp_path): """Create temporary file that auto-cleans up.""" temp_file = tmp_path / "test.txt" temp_file.write_text("test data") yield temp_file # Cleanup automatic with tmp_path ``` ### Fixture Dependency Injection Fixtures can depend on other fixtures: ```python @pytest.fixture def database(): """Base database fixture.""" db = Database(":memory:") db.initialize() yield db db.close() @pytest.fixture def user_service(database): """Service depending on database fixture.""" return UserService(database) @pytest.fixture def authenticated_user(user_service): """User depending on service fixture.""" user = user_service.create_user( username="testuser", email="test@example.com", password="secure" ) yield user user_service.delete_user(user.id) ``` --- ## conftest.py Patterns ### Central Fixture Location Place shared fixtures in `conftest.py` at appropriate levels: ``` tests/ ├── conftest.py # Session/module level fixtures ├── unit/ │ ├── conftest.py # Unit-specific fixtures │ └── test_*.py └── integration/ ├── conftest.py # Integration-specific fixtures └── test_*.py ``` ### Example conftest.py ```python # tests/conftest.py import pytest from app.services import UserService from app.database import Database @pytest.fixture(scope="session") def database_connection(): """Database connection for entire session.""" db = Database(":memory:") db.initialize() db.run_migrations() yield db db.close() @pytest.fixture def user_service(database_connection): """UserService with test database.""" return UserService(database_connection) @pytest.fixture def sample_user_data(): """Standard user data for tests.""" return { "username": "testuser", "email": "test@example.com", "password": "secure123", "age": 25 } @pytest.fixture def temp_config_file(tmp_path): """Create temporary config file.""" config_file = tmp_path / "config.json" config_file.write_text('{"setting": "value"}') return config_file ``` --- ## Parametrized Testing ### Basic Parametrization Use `@pytest.mark.parametrize` for multiple test inputs: ```python import pytest @pytest.mark.parametrize("input,expected", [ ("hello", "HELLO"), ("world", "WORLD"), ("", ""), ("MiXeD", "MIXED"), ]) def test_uppercase(input, expected): """Test uppercase conversion with multiple inputs.""" assert input.upper() == expected ``` ### Multiple Parameters ```python @pytest.mark.parametrize("method,expected_status", [ ("GET", 200), ("POST", 201), ("PUT", 200), ("DELETE", 204), ]) def test_http_methods(client, method, expected_status): """Test different HTTP methods.""" response = client.request(method, "/api/resource") assert response.status_code == expected_status ``` ### Parametrized Fixtures ```python @pytest.mark.parametrize("user_role", ["admin", "user", "guest"]) def test_permission_levels(user_role): """Test different user permission levels.""" user = User(role=user_role) assert user.can_access_dashboard() == (user_role in ["admin", "user"]) ``` --- ## Async Testing ### Marking Async Tests Use `@pytest.mark.asyncio` decorator: ```python import pytest @pytest.mark.asyncio async def test_async_fetch_data(): """Test async data fetching.""" result = await fetch_data_async("user123") assert result is not None assert result["id"] == "user123" @pytest.mark.asyncio async def test_concurrent_requests(): """Test multiple concurrent async operations.""" results = await asyncio.gather( fetch_user(1), fetch_user(2), fetch_user(3), ) assert len(results) == 3 ``` ### Async Fixtures ```python @pytest.fixture async def async_client(): """Async HTTP client fixture.""" client = AsyncHTTPClient() await client.connect() yield client await client.disconnect() @pytest.mark.asyncio async def test_api_call(async_client): """Test with async fixture.""" response = await async_client.get("/api/users") assert response.status_code == 200 ``` --- ## Mocking Patterns ### Mock Objects and Patching ```python from unittest.mock import Mock, patch, MagicMock import pytest def test_send_email_success(): """Test email sending with mocked SMTP.""" # Create mock object mock_smtp = Mock() with patch("smtplib.SMTP", return_value=mock_smtp): result = send_email("test@example.com", "Subject", "Body") # Verify mock was called correctly mock_smtp.send_message.assert_called_once() assert result is True def test_api_call_with_mock(): """Test API call with mocked response.""" with patch("requests.get") as mock_get: mock_get.return_value.json.return_value = {"status": "ok"} result = fetch_api_data() mock_get.assert_called_once_with("https://api.example.com/data") assert result["status"] == "ok" ``` ### Fixture-Based Mocking ```python @pytest.fixture def mock_database(): """Provide mocked database.""" with patch("app.database.Database") as mock_db: mock_db.return_value.query.return_value = {"id": 1, "name": "Test"} yield mock_db def test_user_service_with_mock(mock_database): """Test service with mocked database dependency.""" service = UserService(mock_database.return_value) user = service.get_user(1) assert user["name"] == "Test" @pytest.fixture def mock_api_client(): """Provide mocked API client.""" with patch("app.client.APIClient") as mock: mock.return_value.get.return_value = {"status": "ok"} mock.return_value.post.return_value = {"id": 123} yield mock ``` ### Spy on Real Objects ```python from unittest.mock import MagicMock def test_spy_on_method(): """Spy on actual method calls.""" obj = RealObject() obj.method = MagicMock(side_effect=obj.method) result = obj.method("arg1") obj.method.assert_called_once_with("arg1") assert result == expected_value ``` --- ## Exception Testing ### Testing with Context Managers ```python import pytest from app.exceptions import UserNotFoundError, ValidationError def test_exception_raised(): """Test that correct exception is raised.""" with pytest.raises(UserNotFoundError) as exc_info: user_service.get_user("nonexistent_id") assert "nonexistent_id" in str(exc_info.value) def test_validation_error(): """Test validation error with message check.""" user_data = { "username": "testuser", "email": "invalid-email", # Invalid format "age": 25 } with pytest.raises(ValidationError) as exc_info: user_service.create_user(user_data) assert "email" in str(exc_info.value) def test_exception_type_matching(): """Test matching specific exception type.""" with pytest.raises((ValueError, TypeError)): process_data(None) ``` --- ## pyproject.toml Configuration ### Complete Pytest Configuration ```toml [tool.pytest.ini_options] # Test discovery testpaths = ["tests"] python_files = ["test_*.py", "*_test.py"] python_classes = ["Test*"] python_functions = ["test_*"] # Output and behavior addopts = [ "-v", "--strict-markers", "--tb=short", "--cov=app", "--cov-report=term-missing", "--cov-report=html", ] # Custom markers markers = [ "slow: marks tests as slow (deselect with '-m \"not slow\"')", "integration: marks tests as integration tests", "unit: marks tests as unit tests", "asyncio: marks tests as async tests", ] # Filter warnings as errors filterwarnings = [ "error", "ignore::DeprecationWarning:setuptools", # Ignore specific warnings if needed ] ``` ### Coverage Configuration ```toml [tool.coverage.run] source = ["app"] omit = [ "*/tests/*", "*/migrations/*", "*/__init__.py", "*/__main__.py", ] [tool.coverage.report] exclude_lines = [ "pragma: no cover", "def __repr__", "raise AssertionError", "raise NotImplementedError", "if __name__ == .__main__.:", "if TYPE_CHECKING:",
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub