Install
Terminal · npx$
npx skills add https://github.com/bobmatnyc/claude-mpm-skills --skill pytestWorks with Paperclip
How Pytest fits into a Paperclip company.
Pytest drops into any Paperclip agent that handles - pytest work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.
S
SaaS FactoryPaired
Pre-configured AI company — 18 agents, 18 skills, one-time purchase.
$27$59
Explore packSource file
SKILL.md1445 linesExpandCollapse
---name: pytestdescription: pytest - Python's most powerful testing framework with fixtures, parametrization, plugins, and framework integration for FastAPI, Django, Flaskuser-invocable: falsedisable-model-invocation: trueversion: 1.0.0category: toolchainauthor: Claude MPM Teamlicense: MITprogressive_disclosure: entry_point: summary: "Professional Python testing: fixtures, parametrize, markers, async support, FastAPI/Django/Flask integration, coverage, mocking" when_to_use: "Writing unit tests, integration tests, API testing, TDD workflow, testing async code, database testing, mocking dependencies" quick_start: "1. pip install pytest 2. Create test_*.py files 3. Use fixtures with @pytest.fixture 4. Parametrize with @pytest.mark.parametrize 5. Run: pytest -v"context_limit: 700tags: - pytest - testing - python - tdd - unit-testing - fixtures - mocking - async - fastapi - djangorequires_tools: []--- # pytest - Professional Python Testing ## Overview pytest is the industry-standard Python testing framework, offering powerful features like fixtures, parametrization, markers, plugins, and seamless integration with FastAPI, Django, and Flask. It provides a simple, scalable approach to testing from unit tests to complex integration scenarios. **Key Features**:- Fixture system for dependency injection- Parametrization for data-driven tests- Rich assertion introspection (no need for `self.assertEqual`)- Plugin ecosystem (pytest-cov, pytest-asyncio, pytest-mock, pytest-django)- Async/await support- Parallel test execution with pytest-xdist- Test discovery and organization- Detailed failure reporting **Installation**:```bash# Basic pytestpip install pytest # With common pluginspip install pytest pytest-cov pytest-asyncio pytest-mock # For FastAPI testingpip install pytest httpx pytest-asyncio # For Django testingpip install pytest pytest-django # For async databasespip install pytest-asyncio aiosqlite``` ## Basic Testing Patterns ### 1. Simple Test Functions ```python# test_math.pydef add(a, b): return a + b def test_add(): assert add(2, 3) == 5 assert add(-1, 1) == 0 assert add(0, 0) == 0 def test_add_negative(): assert add(-2, -3) == -5``` **Run tests:**```bash# Discover and run all testspytest # Verbose outputpytest -v # Show print statementspytest -s # Run specific test filepytest test_math.py # Run specific test functionpytest test_math.py::test_add``` ### 2. Test Classes for Organization ```python# test_calculator.pyclass Calculator: def add(self, a, b): return a + b def multiply(self, a, b): return a * b class TestCalculator: def test_add(self): calc = Calculator() assert calc.add(2, 3) == 5 def test_multiply(self): calc = Calculator() assert calc.multiply(4, 5) == 20 def test_add_negative(self): calc = Calculator() assert calc.add(-1, -1) == -2``` ### 3. Assertions and Expected Failures ```pythonimport pytest # Test exception raisingdef divide(a, b): if b == 0: raise ValueError("Cannot divide by zero") return a / b def test_divide_by_zero(): with pytest.raises(ValueError, match="Cannot divide by zero"): divide(10, 0) def test_divide_success(): assert divide(10, 2) == 5.0 # Test approximate equalitydef test_float_comparison(): assert 0.1 + 0.2 == pytest.approx(0.3) # Test containmentdef test_list_contains(): result = [1, 2, 3, 4] assert 3 in result assert len(result) == 4``` ## Fixtures - Dependency Injection ### Basic Fixtures ```python# conftest.pyimport pytest @pytest.fixturedef sample_data(): """Provide sample data for tests.""" return {"name": "Alice", "age": 30, "email": "alice@example.com"} @pytest.fixturedef empty_list(): """Provide an empty list.""" return [] # test_fixtures.pydef test_sample_data(sample_data): assert sample_data["name"] == "Alice" assert sample_data["age"] == 30 def test_empty_list(empty_list): empty_list.append(1) assert len(empty_list) == 1``` ### Fixture Scopes ```pythonimport pytest # Function scope (default) - runs for each test@pytest.fixture(scope="function")def user(): return {"id": 1, "name": "Alice"} # Class scope - runs once per test class@pytest.fixture(scope="class")def database(): db = setup_database() yield db db.close() # Module scope - runs once per test module@pytest.fixture(scope="module")def api_client(): client = APIClient() yield client client.shutdown() # Session scope - runs once for entire test session@pytest.fixture(scope="session")def app_config(): return load_config()``` ### Fixture Setup and Teardown ```pythonimport pytestimport tempfileimport shutil @pytest.fixturedef temp_directory(): """Create a temporary directory for test.""" temp_dir = tempfile.mkdtemp() print(f"Setup: Created {temp_dir}") yield temp_dir # Provide directory to test # Teardown: cleanup after test shutil.rmtree(temp_dir) print(f"Teardown: Removed {temp_dir}") def test_file_creation(temp_directory): file_path = f"{temp_directory}/test.txt" with open(file_path, "w") as f: f.write("test content") assert os.path.exists(file_path)``` ### Fixture Dependencies ```pythonimport pytest @pytest.fixturedef database_connection(): """Database connection.""" conn = connect_to_db() yield conn conn.close() @pytest.fixturedef database_session(database_connection): """Database session depends on connection.""" session = create_session(database_connection) yield session session.rollback() session.close() @pytest.fixturedef user_repository(database_session): """User repository depends on session.""" return UserRepository(database_session) def test_create_user(user_repository): user = user_repository.create(name="Alice", email="alice@example.com") assert user.name == "Alice"``` ## Parametrization - Data-Driven Testing ### Basic Parametrization ```pythonimport pytest @pytest.mark.parametrize("a,b,expected", [ (2, 3, 5), (5, 7, 12), (-1, 1, 0), (0, 0, 0), (100, 200, 300),])def test_add_parametrized(a, b, expected): assert add(a, b) == expected``` ### Multiple Parameters ```python@pytest.mark.parametrize("operation,a,b,expected", [ ("add", 2, 3, 5), ("subtract", 10, 5, 5), ("multiply", 4, 5, 20), ("divide", 10, 2, 5),])def test_calculator_operations(operation, a, b, expected): calc = Calculator() result = getattr(calc, operation)(a, b) assert result == expected``` ### Parametrize with IDs ```python@pytest.mark.parametrize("input_data,expected", [ pytest.param({"name": "Alice"}, "Alice", id="valid_name"), pytest.param({"name": ""}, None, id="empty_name"), pytest.param({}, None, id="missing_name"),], ids=lambda x: x if isinstance(x, str) else None)def test_extract_name(input_data, expected): result = extract_name(input_data) assert result == expected``` ### Indirect Parametrization (Fixtures) ```python@pytest.fixturedef user_data(request): """Create user based on parameter.""" return {"name": request.param, "email": f"{request.param}@example.com"} @pytest.mark.parametrize("user_data", ["Alice", "Bob", "Charlie"], indirect=True)def test_user_creation(user_data): assert "@example.com" in user_data["email"]``` ## Test Markers ### Built-in Markers ```pythonimport pytest # Skip test@pytest.mark.skip(reason="Not implemented yet")def test_future_feature(): pass # Skip conditionally@pytest.mark.skipif(sys.platform == "win32", reason="Unix-only test")def test_unix_specific(): pass # Expected failure@pytest.mark.xfail(reason="Known bug #123")def test_known_bug(): assert False # Slow test marker@pytest.mark.slowdef test_expensive_operation(): time.sleep(5) assert True``` ### Custom Markers ```python# pytest.ini[pytest]markers = slow: marks tests as slow (deselect with '-m "not slow"') integration: marks tests as integration tests unit: marks tests as unit tests smoke: marks tests as smoke tests # test_custom_markers.pyimport pytest @pytest.mark.unitdef test_fast_unit(): assert True @pytest.mark.integration@pytest.mark.slowdef test_slow_integration(): # Integration test with database pass @pytest.mark.smokedef test_critical_path(): # Smoke test for critical functionality pass``` **Run tests by marker:**```bash# Run only unit testspytest -m unit # Run all except slow testspytest -m "not slow" # Run integration testspytest -m integration # Run unit AND integrationpytest -m "unit or integration" # Run smoke tests onlypytest -m smoke``` ## FastAPI Testing ### Basic FastAPI Test Setup ```python# app/main.pyfrom fastapi import FastAPI, HTTPExceptionfrom pydantic import BaseModel app = FastAPI() class Item(BaseModel): name: str price: float @app.get("/")def read_root(): return {"message": "Hello World"} @app.get("/items/{item_id}")def read_item(item_id: int): if item_id == 0: raise HTTPException(status_code=404, detail="Item not found") return {"item_id": item_id, "name": f"Item {item_id}"} @app.post("/items")def create_item(item: Item): return {"name": item.name, "price": item.price, "id": 123}``` ### FastAPI Test Client ```python# conftest.pyimport pytestfrom fastapi.testclient import TestClientfrom app.main import app @pytest.fixturedef client(): """FastAPI test client.""" return TestClient(app) # test_api.pydef test_read_root(client): response = client.get("/") assert response.status_code == 200 assert response.json() == {"message": "Hello World"} def test_read_item(client): response = client.get("/items/1") assert response.status_code == 200 assert response.json() == {"item_id": 1, "name": "Item 1"} def test_read_item_not_found(client): response = client.get("/items/0") assert response.status_code == 404 assert response.json() == {"detail": "Item not found"} def test_create_item(client): response = client.post( "/items", json={"name": "Widget", "price": 9.99} ) assert response.status_code == 200 data = response.json() assert data["name"] == "Widget" assert data["price"] == 9.99 assert "id" in data``` ### Async FastAPI Testing ```python# conftest.pyimport pytestfrom httpx import AsyncClientfrom app.main import app @pytest.fixtureasync def async_client(): """Async test client for FastAPI.""" async with AsyncClient(app=app, base_url="http://test") as client: yield client # test_async_api.pyimport pytest @pytest.mark.asyncioasync def test_read_root_async(async_client): response = await async_client.get("/") assert response.status_code == 200 assert response.json() == {"message": "Hello World"} @pytest.mark.asyncioasync def test_create_item_async(async_client): response = await async_client.post( "/items", json={"name": "Gadget", "price": 19.99} ) assert response.status_code == 200 assert response.json()["name"] == "Gadget"``` ### FastAPI with Database Testing ```python# conftest.pyimport pytestfrom sqlalchemy import create_enginefrom sqlalchemy.orm import sessionmakerfrom app.database import Base, get_dbfrom app.main import app # Test databaseSQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) @pytest.fixture(scope="function")def test_db(): """Create test database.""" Base.metadata.create_all(bind=engine) yield Base.metadata.drop_all(bind=engine) @pytest.fixturedef client(test_db): """Override database dependency.""" def override_get_db(): try: db = TestingSessionLocal() yield db finally: db.close() app.dependency_overrides[get_db] = override_get_db with TestClient(app) as test_client: yield test_client app.dependency_overrides.clear() # test_users.pydef test_create_user(client): response = client.post( "/users", json={"email": "test@example.com", "password": "secret"} ) assert response.status_code == 200 assert response.json()["email"] == "test@example.com" def test_read_users(client): # Create user first client.post("/users", json={"email": "user1@example.com", "password": "pass1"}) client.post("/users", json={"email": "user2@example.com", "password": "pass2"}) # Read users response = client.get("/users") assert response.status_code == 200 assert len(response.json()) == 2``` ## Django Testing ### Django pytest Configuration ```python# pytest.ini[pytest]DJANGO_SETTINGS_MODULE = myproject.settingspython_files = tests.py test_*.py *_tests.py # conftest.pyimport pytestfrom django.conf import settings @pytest.fixture(scope='session')def django_db_setup(): settings.DATABASES['default'] = { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': ':memory:', }``` ### Django Model Testing ```python# models.pyfrom django.db import models class User(models.Model): email = models.EmailField(unique=True) name = models.CharField(max_length=100) is_active = models.BooleanField(default=True) # test_models.pyimport pytestfrom myapp.models import User @pytest.mark.django_dbdef test_create_user(): user = User.objects.create( email="test@example.com", name="Test User" ) assert user.email == "test@example.com" assert user.is_active is True @pytest.mark.django_dbdef test_user_unique_email(): User.objects.create(email="test@example.com", name="User 1") with pytest.raises(Exception): # IntegrityError User.objects.create(email="test@example.com", name="User 2")``` ### Django View Testing ```python# views.pyfrom django.http import JsonResponsefrom django.views import View class UserListView(View): def get(self, request): users = User.objects.all() return JsonResponse({ "users": list(users.values("id", "email", "name")) }) # test_views.pyimport pytestfrom django.test import Clientfrom myapp.models import User @pytest.fixturedef client(): return Client() @pytest.mark.django_dbdef test_user_list_view(client): # Create test data User.objects.create(email="user1@example.com", name="User 1") User.objects.create(email="user2@example.com", name="User 2") # Test view response = client.get("/users/") assert response.status_code == 200 data = response.json() assert len(data["users"]) == 2``` ### Django REST Framework Testing ```python# serializers.pyfrom rest_framework import serializersfrom myapp.models import User class UserSerializer(serializers.ModelSerializer): class Meta: model = User fields = ['id', 'email', 'name', 'is_active'] # views.pyfrom rest_framework import viewsetsfrom myapp.models import Userfrom myapp.serializers import UserSerializer class UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer # test_api.pyimport pytestfrom rest_framework.test import APIClientfrom myapp.models import User @pytest.fixturedef api_client(): return APIClient() @pytest.mark.django_dbdef test_list_users(api_client): User.objects.create(email="user1@example.com", name="User 1") User.objects.create(email="user2@example.com", name="User 2") response = api_client.get("/api/users/") assert response.status_code == 200 assert len(response.data) == 2 @pytest.mark.django_dbdef test_create_user(api_client): data = {"email": "new@example.com", "name": "New User"} response = api_client.post("/api/users/", data) assert response.status_code == 201 assert User.objects.filter(email="new@example.com").exists()``` ## Mocking and Patching ### pytest-mock (pytest.fixture.mocker) ```python# Install: pip install pytest-mock # service.pyimport requests def get_user_data(user_id): response = requests.get(f"https://api.example.com/users/{user_id}") return response.json() # test_service.pydef test_get_user_data(mocker): # Mock requests.get mock_response = mocker.Mock() mock_response.json.return_value = {"id": 1, "name": "Alice"} mocker.patch("requests.get", return_value=mock_response) result = get_user_data(1) assert result["name"] == "Alice"``` ### Mocking Class Methods ```pythonclass UserService: def get_user(self, user_id): # Database call return database.fetch_user(user_id) def get_user_name(self, user_id): user = self.get_user(user_id) return user["name"] def test_get_user_name(mocker): service = UserService() # Mock the get_user method mocker.patch.object( service, "get_user", return_value={"id": 1, "name": "Alice"} ) result = service.get_user_name(1) assert result == "Alice"``` ### Mocking with Side Effects ```pythondef test_retry_on_failure(mocker): # First call fails, second succeeds mock_api = mocker.patch("requests.get") mock_api.side_effect = [ requests.exceptions.Timeout(), # First call mocker.Mock(json=lambda: {"status": "ok"}) # Second call ] result = api_call_with_retry() assert result["status"] == "ok" assert mock_api.call_count == 2``` ### Spy on Calls ```pythondef test_function_called_correctly(mocker): spy = mocker.spy(module, "function_name") # Call code that uses the function module.run_workflow() # Verify it was called assert spy.call_count == 1 spy.assert_called_once_with(arg1="value", arg2=42)``` ## Coverage and Reporting ### pytest-cov Configuration ```bash# Installpip install pytest-cov # Run with coveragepytest --cov=app --cov-report=html --cov-report=term # Generate coverage reportpytest --cov=app --cov-report=term-missing # Coverage with minimum thresholdpytest --cov=app --cov-fail-under=80``` ### pytest.ini Coverage Configuration ```ini# pytest.ini[pytest]addopts = --cov=app --cov-report=html --cov-report=term-missing --cov-fail-under=80 -vtestpaths = testspython_files = test_*.pypython_classes = Test*python_functions = test_*``` ### Coverage Reports ```bash# HTML report (opens in browser)pytest --cov=app --cov-report=htmlopen htmlcov/index.html # Terminal report with missing linespytest --cov=app --cov-report=term-missing # XML report (for CI/CD)pytest --cov=app --cov-report=xml # JSON reportpytest --cov=app --cov-report=json``` ## Async Testing ### pytest-asyncio ```python# Install: pip install pytest-asyncio # conftest.pyimport pytest # Enable asyncio modepytest_plugins = ('pytest_asyncio',) # async_service.pyimport asyncioimport aiohttp async def fetch_data(url): async with aiohttp.ClientSession() as session: async with session.get(url) as response: return await response.json() # test_async_service.pyimport pytest @pytest.mark.asyncioasync def test_fetch_data(mocker): # Mock aiohttp response mock_response = mocker.AsyncMock() mock_response.json.return_value = {"data": "test"} mock_session = mocker.AsyncMock() mock_session.__aenter__.return_value.get.return_value.__aenter__.return_value = mock_response mocker.patch("aiohttp.ClientSession", return_value=mock_session) result = await fetch_data("https://api.example.com/data") assert result["data"] == "test"``` ### Async Fixtures ```python@pytest.fixtureasync def async_db_session(): """Async 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.asyncioasync def test_create_user_async(async_db_session): user = User(email="test@example.com", name="Test") async_db_session.add(user) await async_db_session.commit() result = await async_db_session.execute( select(User).where(User.email == "test@example.com") ) assert result.scalar_one().name == "Test"``` ## Local pytest Profiles (Your Repos) Common settings from your projects' `pyproject.toml`: - `asyncio_mode = "auto"` (default in mcp-browser, mcp-memory, claude-mpm, edgar)- `addopts` includes `--strict-markers` and `--strict-config` for CI consistency- Coverage flags: `--cov=<package>`, `--cov-report=term-missing`, `--cov-report=xml`- Selective ignores (mcp-vector-search): `--ignore=tests/manual`, `--ignore=tests/e2e`- `pythonpath = ["src"]` for editable import resolution (mcp-ticketer) Typical markers: - `unit`, `integration`, `e2e`- `slow`, `benchmark`, `performance`- `requires_api` (edgar) Reference: see `pyproject.toml` in `claude-mpm`, `edgar`, `mcp-vector-search`, `mcp-ticketer`, and `kuzu-memory` for full lists. ## Best Practices ### 1. Test Organization ```project/├── app/│ ├── __init__.py│ ├── main.py│ ├── models.py│ └── services.py├── tests/│ ├── __init__.py│ ├── conftest.py # Shared fixtures│ ├── test_models.py # Model tests│ ├── test_services.py # Service tests│ ├── test_api.py # API tests│ └── integration/│ ├── __init__.py│ └── test_workflows.py└── pytest.ini``` ### 2. Naming Conventions ```python# ✅ GOOD: Clear test namesdef test_user_creation_with_valid_email(): pass def test_user_creation_raises_error_for_duplicate_email(): pass # ❌ BAD: Vague namesdef test_user1(): pass def test_case2(): pass``` ### 3. Arrange-Act-Assert Pattern ```pythondef test_user_service_creates_user(): # Arrange: Setup test data and dependencies service = UserService(database=mock_db) user_data = {"email": "test@example.com", "name": "Test"} # Act: Perform the action being tested result = service.create_user(user_data) # Assert: Verify the outcome assert result.email == "test@example.com" assert result.id is not None``` ### 4. Use Fixtures for Common Setup ```python# ❌ BAD: Repeated setupdef test_user_creation(): db = setup_database() user = create_user(db) assert user.id is not None db.close() def test_user_deletion(): db = setup_database() user = create_user(db) delete_user(db, user.id) db.close() # ✅ GOOD: Fixture-based setup@pytest.fixturedef db(): database = setup_database() yield database database.close() @pytest.fixturedef user(db): return create_user(db) def test_user_creation(user): assert user.id is not None def test_user_deletion(db, user): delete_user(db, user.id) assert not user_exists(db, user.id)``` ### 5. Parametrize Similar Tests ```python# ❌ BAD: Duplicate test codedef test_add_positive(): assert add(2, 3) == 5 def test_add_negative(): assert add(-2, -3) == -5 def test_add_zero(): assert add(0, 0) == 0 # ✅ GOOD: Parametrized tests@pytest.mark.parametrize("a,b,expected", [ (2, 3, 5), (-2, -3, -5), (0, 0, 0),])def test_add(a, b, expected): assert add(a, b) == expected``` ### 6. Test One Thing Per Test ```python# ❌ BAD: Testing multiple thingsdef test_user_workflow(): user = create_user() assert user.id is not None updated = update_user(user.id, name="New Name") assert updated.name == "New Name" deleted = delete_user(user.id) assert deleted is True # ✅ GOOD: Separate testsdef test_user_creation(): user = create_user() assert user.id is not None def test_user_update(): user = create_user() updated = update_user(user.id, name="New Name") assert updated.name == "New Name" def test_user_deletion(): user = create_user() result = delete_user(user.id) assert result is True``` ### 7. Use Markers for Test Organization ```python@pytest.mark.unitdef test_pure_function(): pass @pytest.mark.integration@pytest.mark.slowdef test_database_integration(): pass @pytest.mark.smokedef test_critical_path(): pass``` ### 8. Mock External Dependencies ```python# ✅ GOOD: Mock external APIdef test_fetch_user_data(mocker): mocker.patch("requests.get", return_value=mock_response) result = fetch_user_data(user_id=1) assert result["name"] == "Alice" # ❌ BAD: Real API call in testdef test_fetch_user_data(): result = fetch_user_data(user_id=1) # Real HTTP request! assert result["name"] == "Alice"``` ## Common Pitfalls ### ❌ Anti-Pattern 1: Test Depends on Execution Order ```python# WRONG: Tests should be independentclass TestUserWorkflow: user_id = None def test_create_user(self): user = create_user() TestUserWorkflow.user_id = user.id def test_update_user(self): # Fails if test_create_user didn't run first! update_user(TestUserWorkflow.user_id, name="New")``` **Correct:**```python@pytest.fixturedef created_user(): return create_user() def test_create_user(created_user): assert created_user.id is not None def test_update_user(created_user): update_user(created_user.id, name="New")``` ### ❌ Anti-Pattern 2: Not Cleaning Up Resources ```python# WRONG: Database not cleaned updef test_user_creation(): db = setup_database() user = create_user(db) assert user.id is not None # Database connection not closed!``` **Correct:**```python@pytest.fixturedef db(): database = setup_database() yield database database.close() # Cleanup``` ### ❌ Anti-Pattern 3: Testing Implementation Details ```python# WRONG: Testing internal implementationdef test_user_service_uses_cache(): service = UserService() service.get_user(1) assert service._cache.has_key(1) # Testing internal cache!``` **Correct:**```python# Test behavior, not implementationdef test_user_service_returns_user(): service = UserService() user = service.get_user(1) assert user.id == 1``` ### ❌ Anti-Pattern 4: Not Using pytest Features ```python# WRONG: Using unittest assertionsimport unittest def test_addition(): result = add(2, 3) unittest.TestCase().assertEqual(result, 5)``` **Correct:**```python# Use pytest's rich assertionsdef test_addition(): assert add(2, 3) == 5``` ### ❌ Anti-Pattern 5: Overly Complex Fixtures ```python# WRONG: Fixture does too much@pytest.fixturedef everything(): db = setup_db() user = create_user(db) session = login(user) cache = setup_cache() # ... too many things! return {"db": db, "user": user, "session": session, "cache": cache}``` **Correct:**```python# Separate, composable fixtures@pytest.fixturedef db(): return setup_db() @pytest.fixturedef user(db): return create_user(db) @pytest.fixturedef session(user): return login(user)``` ## Quick Reference ### Common Commands ```bash# Run all testspytest # Verbose outputpytest -v # Show print statementspytest -s # Run specific filepytest tests/test_api.py # Run specific testpytest tests/test_api.py::test_create_user # Run by markerpytest -m unitpytest -m "not slow" # Run with coveragepytest --cov=app --cov-report=html # Parallel executionpytest -n auto # Requires pytest-xdist # Stop on first failurepytest -x # Show local variables on failurepytest -l # Run last failed testspytest --lf # Run failed tests firstpytest --ff``` ### pytest.ini Template ```ini[pytest]# Minimum pytest versionminversion = 7.0 # Test discovery patternspython_files = test_*.py *_test.pypython_classes = Test*python_functions = test_* # Test pathstestpaths = tests # Command line optionsaddopts = -v --strict-markers --cov=app --cov-report=html --cov-report=term-missing --cov-fail-under=80 # Markersmarkers = unit: Unit tests integration: Integration tests slow: Slow-running tests smoke: Smoke tests for critical paths # Django settings (if using Django)DJANGO_SETTINGS_MODULE = myproject.settings # Asyncio modeasyncio_mode = auto``` ### conftest.py Template ```python# conftest.pyimport pytestfrom fastapi.testclient import TestClientfrom app.main import app # FastAPI client fixture@pytest.fixturedef client(): return TestClient(app) # Database fixture@pytest.fixture(scope="function")def db(): database = setup_test_database() yield database database.close() # Mock user fixture@pytest.fixturedef mock_user(): return {"id": 1, "email": "test@example.com", "name": "Test User"} # Custom pytest configurationdef pytest_configure(config): config.addinivalue_line("markers", "api: API tests") config.addinivalue_line("markers", "db: Database tests")``` ## Resources - **Official Documentation**: https://docs.pytest.org/- **pytest-asyncio**: https://pytest-asyncio.readthedocs.io/- **pytest-cov**: https://pytest-cov.readthedocs.io/- **pytest-mock**: https://pytest-mock.readthedocs.io/- **pytest-django**: https://pytest-django.readthedocs.io/- **FastAPI Testing**: https://fastapi.tiangolo.com/tutorial/testing/ ## Related Skills When using pytest, consider these complementary skills: - **fastapi-local-dev**: FastAPI development server patterns and test fixtures- **test-driven-development**: Complete TDD workflow (RED/GREEN/REFACTOR cycle)- **systematic-debugging**: Root cause investigation for failing tests ### Quick TDD Workflow Reference (Inlined for Standalone Use) **RED → GREEN → REFACTOR Cycle:** 1. **RED Phase: Write Failing Test** ```python def test_should_authenticate_user_when_credentials_valid(): # Test that describes desired behavior user = User(username='alice', password='secret123') result = authenticate(user) assert result.is_authenticated is True # This test will fail because authenticate() doesn't exist yet ``` 2. **GREEN Phase: Make It Pass** ```python def authenticate(user): # Minimum code to pass the test if user.username == 'alice' and user.password == 'secret123': return AuthResult(is_authenticated=True) return AuthResult(is_authenticated=False) ``` 3. **REFACTOR Phase: Improve Code** ```python def authenticate(user): # Clean up while keeping tests green hashed_password = hash_password(user.password) stored_user = database.get_user(user.username) return AuthResult( is_authenticated=(stored_user.password_hash == hashed_password) ) ``` **Test Structure: Arrange-Act-Assert (AAA)**```pythondef test_user_creation(): # Arrange: Set up test data user_data = {'username': 'alice', 'email': 'alice@example.com'} # Act: Perform the action user = create_user(user_data) # Assert: Verify outcome assert user.username == 'alice' assert user.email == 'alice@example.com'``` ### Quick Debugging Reference (Inlined for Standalone Use) **Phase 1: Root Cause Investigation**- Read error messages completely (stack traces, line numbers)- Reproduce consistently (document exact steps)- Check recent changes (git log, git diff)- Understand what changed and why it might cause failure **Phase 2: Isolate the Problem**```python# Use pytest's built-in debuggingpytest tests/test_auth.py -vv --pdb # Drop into debugger on failurepytest tests/test_auth.py -x # Stop on first failurepytest tests/test_auth.py -k "auth" # Run only auth-related tests # Add strategic print/loggingdef test_complex_workflow(): user = create_user({'username': 'test'}) print(f"DEBUG: Created user {user.id}") # Visible with pytest -s result = process_user(user) print(f"DEBUG: Result status {result.status}") assert result.success``` **Phase 3: Fix Root Cause**- Fix the underlying problem, not symptoms- Add regression test to prevent recurrence- Verify fix doesn't break other tests **Phase 4: Verify Solution**```bash# Run full test suitepytest # Run with coveragepytest --cov=src --cov-report=html # Verify specific test patternspytest -k "auth or login" -v``` [Full TDD and debugging workflows available in respective skills if deployed together] --- **pytest Version Compatibility:** This skill covers pytest 7.0+ and reflects current best practices for Python testing in 2025.