Skip to content

Commit 0344584

Browse files
emcdclaude
andcommitted
Copier Template: Add custom slash command for systematic test writing.
Create write-tests.md command that guides LLMs through comprehensive test creation following project testing principles. Command emphasizes dependency injection over monkey-patching, performance-conscious patterns with pyfakefs, and systematic numbering conventions. Includes safety requirements, code analysis phases, and success criteria checklist to ensure tests improve coverage while maintaining code quality and architectural integrity. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent e8dd534 commit 0344584

2 files changed

Lines changed: 201 additions & 18 deletions

File tree

documentation/common/tests.rst

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,10 @@ Test Directory Structure
7373
├── fixtures.py # Common test fixtures and utilities
7474
├── test_000_package.py # Package-level tests
7575
├── test_010_internals.py # Internal utilities
76-
├── test_100_core.py # Core public API
77-
├── test_200_advanced.py # Advanced features
78-
└── test_900_integration.py # Integration tests
76+
├── test_100_<layer 0>.py # Lowest levels of public API
77+
├── test_200_<layer 1>.py # Lower levels of public API
78+
├── ... # Higher levels of API
79+
└── test_500_integration.py # Top levels of API; Integration tests
7980

8081
Numbering System
8182
-------------------------------------------------------------------------------
@@ -96,10 +97,10 @@ Within test modules, number test functions similarly::
9697

9798
def test_000_basic_functionality():
9899
''' Basic feature works as expected. '''
99-
100+
100101
def test_100_error_handling():
101102
''' Error conditions are handled gracefully. '''
102-
103+
103104
def test_200_advanced_scenarios():
104105
''' Advanced usage patterns work correctly. '''
105106

@@ -133,7 +134,7 @@ The most important testing pattern. Inject dependencies via parameters::
133134
async def test_process_data():
134135
def mock_processor( data ):
135136
return f"processed: {data}"
136-
137+
137138
result = await process_data( "test", processor = mock_processor )
138139
assert result == "processed: test"
139140

@@ -142,7 +143,7 @@ Constructor injection for objects::
142143
@dataclass( frozen = True )
143144
class DataProcessor:
144145
validator: Callable[ [ str ], bool ] = default_validator
145-
146+
146147
def process( self, data: str ) -> str:
147148
if not self.validator( data ):
148149
raise ValueError( "Invalid data" )
@@ -152,7 +153,7 @@ Constructor injection for objects::
152153
def test_data_processor():
153154
def always_valid( data ):
154155
return True
155-
156+
156157
processor = DataProcessor( validator = always_valid )
157158
result = processor.process( "test" )
158159
assert result == "TEST"
@@ -181,7 +182,7 @@ only when necessary::
181182
temp_path = Path( temp_dir )
182183
config_file = temp_path / 'config.toml'
183184
config_file.write_text( '[section]\nkey = "value"' )
184-
185+
185186
result = await async_process_config_file( config_file )
186187
assert result.key == 'value'
187188

@@ -198,14 +199,14 @@ When to Mock
198199
Example with third-party mock::
199200

200201
import httpx
201-
202+
202203
def test_http_client():
203204
def handler( request ):
204205
return httpx.Response( 200, json = { "result": "success" } )
205-
206+
206207
transport = httpx.MockTransport( handler )
207208
client = httpx.Client( transport = transport )
208-
209+
209210
response = client.get( "https://example.com/api" )
210211
assert response.json() == { "result": "success" }
211212

@@ -250,7 +251,7 @@ Development Environment
250251
* **Always use hatch environment** for all testing commands::
251252

252253
hatch --env develop run pytest # run tests
253-
hatch --env develop run linters # run linters
254+
hatch --env develop run linters # run linters
254255
hatch --env develop run testers # run full test suite with coverage
255256

256257
* **Test performance**: The elapsed time reported by ``pytest`` should be
@@ -287,11 +288,11 @@ Mock frame chains for call stack simulation (document justification)::
287288
external_frame = MagicMock()
288289
external_frame.f_code.co_filename = '/external/caller.py'
289290
external_frame.f_back = None
290-
291+
291292
internal_frame = MagicMock()
292293
internal_frame.f_code.co_filename = '/internal/module.py'
293294
internal_frame.f_back = external_frame
294-
295+
295296
with patch( 'inspect.currentframe', return_value = internal_frame ):
296297
result = module._discover_invoker_location()
297298
assert result == Path( '/external' )
@@ -302,7 +303,7 @@ Resource Management
302303
Use ``ExitStack`` for multiple temporary resources::
303304

304305
from contextlib import ExitStack
305-
306+
306307
def test_multiple_temp_files():
307308
with ExitStack() as stack:
308309
temp1 = stack.enter_context(
@@ -321,7 +322,7 @@ Test error conditions and recovery paths::
321322
config[ 'application' ][ 'safe_mode' ] = True
322323
except Exception:
323324
config[ 'fallback' ] = True # Apply fallback
324-
325+
325326
@pytest.mark.asyncio
326327
async def test_error_recovery():
327328
async with contextlib.AsyncExitStack() as exits:
@@ -406,4 +407,4 @@ Benefits of This Approach
406407
3. **Maintainable tests** - less fragile than monkey-patching
407408
4. **Preserved architecture** - immutability provides thread safety
408409
5. **Optimized performance** - strategic use of in-memory filesystems
409-
6. **Comprehensive coverage** - systematic targeting of uncovered branches
410+
6. **Comprehensive coverage** - systematic targeting of uncovered branches
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
---
2+
allowed-tools: Bash(hatch --env develop run:*), Bash(git status), Bash(git log:*), Bash(echo:*), Bash(ls:*), Bash(find:*), LS, Read, Glob, Grep, Write, Edit, MultiEdit
3+
description: Write comprehensive tests following project testing guidelines and improve coverage
4+
---
5+
6+
# Write Tests
7+
8+
**NOTE: This is an experimental workflow! If anything seems unclear or missing,
9+
please stop for consultation with the user.**
10+
11+
For systematic test creation following project testing guidelines and conventions.
12+
13+
Test requirements: `$ARGUMENTS`
14+
15+
**CRITICAL**: Apply project testing principles consistently.
16+
**HALT if**:
17+
- No test requirements are provided
18+
- Target code cannot be analyzed
19+
- Testing principles would be violated
20+
21+
## Context
22+
23+
- Current git status: !`git status --porcelain`
24+
- Current branch: !`git branch --show-current`
25+
- Current test coverage: !`hatch --env develop run coverage report --skip-covered || echo "No coverage data available"`
26+
- Existing test structure: !`find tests -name "*.py" | head -20`
27+
- Test README: !`ls tests/README.md 2>/dev/null && echo "Present" || echo "Missing"`
28+
29+
## Prerequisites
30+
31+
Ensure that you:
32+
- have verified access to / read target code modules
33+
- understand what specific tests need to be written
34+
- have read any relevant `CLAUDE.md` file
35+
- understand the [test-writing guidelines](https://raw.githubusercontent.com/emcd/python-project-common/refs/heads/master/documentation/common/tests.rst)
36+
37+
## Testing Principles (from project guidelines)
38+
39+
**Core Principles:**
40+
1. **Dependency Injection Over Monkey-Patching**: Use injectable dependencies
41+
for testability
42+
2. **Performance-Conscious**: Prefer in-memory filesystems (pyfakefs) over temp
43+
directories
44+
3. **Avoid Monkey-Patching**: Never patch internal code; use dependency
45+
injection instead
46+
4. **100% Coverage Goal**: Aim for complete line and branch coverage
47+
5. **Test Behavior, Not Implementation**: Focus on observable behavior and
48+
contracts
49+
50+
**Anti-Patterns to Avoid:**
51+
- Monkey-patching internal code (will fail with immutable objects)
52+
- Excessive mocking of internal components
53+
- Testing implementation details vs. behavior
54+
- Using temp directories when pyfakefs suffices
55+
56+
**Organization:**
57+
- Follow the systematic numbering conventions detailed in the test guidelines
58+
59+
## Safety Requirements
60+
61+
**CRITICAL**: You MUST halt the process and consult with the user if ANY of the
62+
following occur:
63+
64+
- **Anti-Pattern Detection**: If proposed tests violate project principles
65+
- **Coverage Regression**: If tests would reduce existing coverage
66+
- **Architecture Conflicts**: If tests require monkey-patching internal code
67+
- **Numbering Conflicts**: If test numbering clashes with existing conventions
68+
- **Missing Dependencies**: If required test fixtures or dependencies are
69+
unavailable
70+
71+
**Your responsibilities:**
72+
- Follow project style and test-writing conventions exactly.
73+
- Use dependency injection patterns consistently.
74+
- Prefer pyfakefs for filesystem operations.
75+
- Maintain systematic test numbering.
76+
- Ensure tests validate behavior, not implementation.
77+
78+
## Test Writing Process
79+
80+
Execute the following steps for test requirements: `$ARGUMENTS`
81+
82+
### 1. Code Analysis Phase
83+
Examine the target code to understand testing needs:
84+
85+
**For each target file:**
86+
- Read the source code to understand public API
87+
- Identify functions/classes that need testing
88+
- Note dependency injection points
89+
- Check for existing test coverage gaps
90+
91+
### 2. Test Structure Planning
92+
Determine appropriate test organization and categories:
93+
94+
**Review existing test structure and plan test numbering following project conventions.**
95+
96+
**Test Categories to Include:**
97+
- **Basic Functionality Tests (000-099):** Happy path scenarios, input validation, basic error conditions
98+
- **Feature-Specific Tests (100+ blocks):** Each public function/class gets its own 100-block with normal usage patterns, edge cases, and error handling
99+
- **Integration Tests (higher numbers):** Cross-module interactions and end-to-end workflows
100+
101+
### 3. Test Implementation
102+
Create tests following project conventions:
103+
104+
**Key Implementation Guidelines:**
105+
- Use dependency injection for all external dependencies
106+
- Prefer `pyfakefs.Patcher()` for filesystem operations
107+
- Mock only third-party services, never internal code
108+
- Include docstrings explaining what behavior is tested
109+
- Follow existing naming conventions and code style
110+
111+
### 5. Coverage Validation
112+
Verify tests improve coverage without regressions:
113+
```bash
114+
hatch --env develop run testers
115+
hatch --env develop run coverage report --show-missing
116+
```
117+
118+
**CRITICAL - VERIFY COVERAGE IMPROVEMENT:**
119+
- Run full test suite to ensure no regressions
120+
- Check that new tests increase overall coverage
121+
- Verify no existing functionality is broken
122+
- Confirm tests follow project numbering conventions
123+
124+
### 6. Code Quality Validation
125+
Ensure tests meet project standards:
126+
```bash
127+
hatch --env develop run linters
128+
```
129+
130+
**Requirements:**
131+
- All linting checks must pass
132+
- No violations of project coding standards
133+
- Test docstrings are clear and descriptive
134+
- Proper imports and dependencies
135+
136+
## Test Pattern Examples
137+
138+
**Dependency Injection Pattern:**
139+
```python
140+
async def test_100_process_with_custom_processor( ):
141+
''' Process function accepts custom processor via injection. '''
142+
def mock_processor( data ):
143+
return f"processed: {data}"
144+
145+
result = await process_data( "test", processor = mock_processor )
146+
assert result == "processed: test"
147+
```
148+
149+
**Filesystem Operations (Preferred):**
150+
```python
151+
def test_200_config_file_processing( ):
152+
''' Configuration files are processed correctly. '''
153+
with Patcher( ) as patcher:
154+
fs = patcher.fs
155+
fs.create_file( '/fake/config.toml', contents = '[section]\nkey="value"' )
156+
157+
result = process_config_file( Path( '/fake/config.toml' ) )
158+
assert result.key == 'value'
159+
```
160+
161+
**Error Handling:**
162+
```python
163+
def test_300_invalid_input_handling( ):
164+
''' Invalid input raises appropriate exceptions. '''
165+
with pytest.raises( ValueError, match = "Invalid data format" ):
166+
process_invalid_data( "malformed" )
167+
```
168+
169+
## Success Criteria
170+
171+
Tests are complete when:
172+
- [ ] Coverage has measurably improved
173+
- [ ] All new tests pass consistently
174+
- [ ] No existing tests are broken
175+
- [ ] Linting passes without issues
176+
- [ ] Tests follow project numbering conventions
177+
- [ ] Dependency injection is used appropriately
178+
- [ ] No monkey-patching of internal code
179+
- [ ] Performance-conscious patterns are applied
180+
181+
**Note**: Always run full validation (`hatch --env develop run testers && hatch
182+
--env develop run linters`) before considering the task complete.

0 commit comments

Comments
 (0)