Pytest fixture design, conftest.py hierarchy, and DRY test code patterns. Identifies duplicate fixtures, plans fixture scope, and designs conftest.py structure...
Fixture management and conftest.py design for Python test suites.
# 1. Find existing conftest files
serena.search_for_pattern("conftest\\.py", paths_include_glob="**/tests/**")
# 2. Identify fixture definitions
serena.search_for_pattern("@pytest\\.fixture", paths_include_glob="**/tests/**")
# 3. Find duplicate fixture names
jetbrains.search_in_files_by_text("def sample_", fileMask="test_*.py")
# 4. Check existing helpers
serena.get_symbols_overview(relative_path="tests/")
tests/
āāā conftest.py # Shared across ALL tests
ā āāā Fixtures: session-scoped, DB connections, API clients
āāā cli/
ā āāā conftest.py # CLI-specific fixtures
ā ā āāā Fixtures: CLI runner, mock console, sample paths
ā āāā test_*.py
āāā core/
āāā conftest.py # Core-specific fixtures
ā āāā Fixtures: domain models, sample data
āāā {module}/
āāā conftest.py # Module-specific (rarely needed)
āāā test_*.py
| Scope | Recreated | Use When |
|---|---|---|
function |
Each test | Default; mutable state |
class |
Each class | Class-based tests, shared setup |
module |
Each file | Expensive setup, immutable data |
session |
Once | DB connections, API clients |
Rule: Start with function, widen scope only for performance.
@pytest.fixture
def make_user():
"""Factory that creates users with custom attributes."""
def _make_user(name: str = "test", role: str = "user") -> User:
return User(name=name, role=role)
return _make_user
# Usage in test
def test_admin_access(make_user):
admin = make_user(role="admin")
assert admin.can_access_admin_panel()
@pytest.fixture(params=["sqlite", "postgres", "mysql"])
def database(request):
"""Runs test with each database type."""
return create_connection(request.param)
@pytest.fixture
def temp_file(tmp_path):
"""Provides temp file and cleans up after test."""
path = tmp_path / "test.txt"
path.write_text("content")
yield path
# Cleanup happens after test
Run before creating fixtures:
# Find fixtures with same name
grep -r "@pytest.fixture" tests/ | grep "def " | cut -d: -f2 | sort | uniq -d
# Find common helper patterns
grep -rh "def get_output\|def create_\|def make_\|def sample_" tests/
"""
Fixtures for {directory} tests.
Shared fixtures:
- {fixture_name}: {description}
Dependencies:
- Uses: {parent_fixture} from parent conftest
"""
import pytest
@pytest.fixture
def fixture_name():
"""One-line description."""
return value
| Pattern | Problem | Solution |
|---|---|---|
| Same fixture in 2+ files | Maintenance burden | Move to shared conftest |
get_output() in multiple files |
Helper duplication | Create helpers.py |
| Copy-paste setup code | Test fragility | Factory fixture |
| Inline data creation | Readability | Data fixtures |
tests/
āāā conftest.py # Fixtures only
āāā helpers.py # Shared utility functions
ā āāā get_output()
ā āāā assert_table_contains()
ā āāā create_mock_response()
āāā fixtures/ # Complex fixture data
āāā sample_metrics.json
āāā sample_datasets.yaml
## Fixture Analysis: {directory}
### Existing Fixtures
| Location | Fixture | Scope | Used By |
|----------|---------|-------|---------|
| tests/conftest.py | db_session | session | 15 tests |
| tests/cli/conftest.py | cli_runner | function | 8 tests |
### Issues Found
1. **Duplicate**: `sample_project_path` in test_a.py and test_b.py
- Action: Move to tests/cli/conftest.py
2. **Missing conftest**: tests/core/workflow/ has no conftest
- Action: Create with shared fixtures
### Recommendations
- [ ] Create tests/cli/conftest.py with: cli_runner, mock_console
- [ ] Move get_output() to tests/helpers.py
- [ ] Change db_session scope from function to module