ADR-0049: Pytest Fixture Consolidation and Organization
Date: 2025-11-07Status
AcceptedCategory
Testing & QualityDecision Makers
Test Infrastructure TeamRelated ADRs
ADR-0042 (Dependency Injection)Context
During a comprehensive test infrastructure review prompted by OpenAI Codex findings, we discovered 25 duplicateinit_test_observability autouse fixtures scattered across test modules. This created several issues:
- Performance Overhead: Observability initialization ran 25 times (once per module) instead of once per session
- Maintenance Burden: Changes to observability initialization required updating 25+ files
- Potential Conflicts: Multiple initializations could lead to race conditions or state pollution
- Confusion: Unclear which fixture would run first or take precedence
- Anti-Pattern: Violates DRY (Don’t Repeat Yourself) principle
Investigation Findings
The investigation revealed:-
25 duplicate fixtures with identical implementations across:
tests/test_auth.py:22tests/test_health_check.py:12tests/property/test_auth_properties.py:57- …and 22 more files
- Performance Impact: Each fixture initialized logging, tracing, and metrics systems
-
Test Execution: Using
pytest --collect-onlyshowed 3115 tests total -
Fixture Scope: All duplicates were
scope="module"withautouse=True
TDD Validation Approach
Following Test-Driven Development principles:- RED Phase: Created
tests/test_fixture_organization.pyto detect duplicates - test FAILED - GREEN Phase: Consolidated fixtures into
tests/conftest.py- test PASSED - REFACTOR Phase: Added automation and preventive measures
Decision
We will consolidate all session/module-scoped autouse fixtures intotests/conftest.py and enforce this pattern through automated tooling.
Fixture Organization Rules
-
Session-scoped autouse fixtures MUST be in
tests/conftest.py- Example: observability initialization, global test configuration
- Rationale: Single initialization point, clear lifecycle
-
Module-scoped autouse fixtures SHOULD be in
tests/conftest.py- Exception: Test-specific configuration that doesn’t apply globally
- Must be documented if placed elsewhere
-
Function-scoped fixtures CAN be in individual test files
- These are test-specific and don’t create overhead
- Should still avoid duplication
-
Test-specific configuration fixtures should NOT be autouse
- Use
pytest.mark.usefixtures()or direct fixture usage - Example:
test_circuit_breaker_configin conftest.py
- Use
Implementation
Consolidated Fixture (tests/conftest.py:70-102):
Consequences
Positive
-
Performance Improvement
- Observability initialized once per session instead of 25 times
- Estimated 96% reduction in initialization overhead
- Faster test suite execution
-
Maintainability
- Single source of truth in
tests/conftest.py - Changes only need to be made in one place
- Clear fixture lifecycle and dependencies
- Single source of truth in
-
Code Quality
- Removed 476 lines of duplicate code
- Improved code organization
- Better adherence to DRY principle
-
Reliability
- Eliminated potential race conditions
- Consistent test environment across all modules
- Predictable fixture execution order
-
Developer Experience
- Clearer test structure
- Easier to understand fixture behavior
- Better documentation
Negative
-
Initial Learning Curve
- Developers need to know where fixtures are defined
- Requires understanding of pytest fixture scopes
-
Less Flexibility
- Cannot easily override observability settings per module
- Module-specific configuration requires different patterns
Mitigation Strategies
- Documentation: Updated
~/.claude/CLAUDE.mdwith fixture best practices - Enforcement: Created pre-commit hook to prevent duplicate fixtures
- Testing: Added
tests/test_fixture_organization.pyto detect violations - Plugin: Created
tests/conftest_fixtures_plugin.pyfor runtime enforcement - Automation: Created scripts to detect and remove duplicates
Preventive Measures
1. Test-Based Prevention
2. Pre-Commit Hook
3. Runtime Plugin
4. Automation Scripts
scripts/remove_duplicate_fixtures.py- AST-based fixture removalscripts/fix_decorator_leftover.py- Cleanup orphaned decorators
Alternatives Considered
Option 1: Keep Duplicates, Document Pattern
- Rejected: Doesn’t solve performance or maintenance issues
- Maintains status quo with known problems
Option 2: Use pytest-fixtures Plugin
- Rejected: Additional dependency, doesn’t solve our specific issue
- Would still need custom validation logic
Option 3: Module-Level Configuration Files
- Rejected: More complex, harder to maintain
- Doesn’t align with pytest best practices
Option 4: Fixture Factories
- Considered: Could work but adds complexity
- Session-scoped autouse fixture is simpler and more explicit
References
- Evidence Report:
docs-internal/CODEX_FINDINGS_VALIDATION_REPORT.md - Test Implementation:
tests/test_fixture_organization.py - Plugin Implementation:
tests/conftest_fixtures_plugin.py - Pre-commit Hook:
.pre-commit-config.yaml:226-243 - Global TDD Guidelines:
~/.claude/CLAUDE.md(Pytest Fixture Best Practices) - Pytest Documentation: https://docs.pytest.org/en/stable/how-to/fixtures.html#scope-sharing-fixtures-across-classes-modules-packages-or-session
Implementation Status
- ✅ Complete: Fixture consolidation
- ✅ Complete: Test-based validation
- ✅ Complete: Pre-commit hook
- ✅ Complete: Runtime plugin
- ✅ Complete: Documentation updates
- ✅ Complete: Automation scripts
Metrics
- Files Modified: 35 files
- Lines Removed: -476 lines (duplicate code)
- Lines Added: +969 lines (tests, docs, automation)
- Net Change: +482 lines
- Test Coverage: 100% of fixture organization rules
- Validation: 3115+ tests passing with consolidated fixtures
Review and Update
This ADR should be reviewed when:- Pytest major version updates
- Significant changes to test infrastructure
- New fixture patterns emerge
- Performance issues with current approach