0
# Pytest Plugin
1
2
Pytest integration for enhanced testing workflows with automatic assert statement insertion, test failure reporting, and development-time debugging utilities. The plugin provides `insert_assert()` functionality for dynamic test generation and integrates devtools debugging capabilities into the pytest environment.
3
4
## Capabilities
5
6
### Insert Assert Function
7
8
Dynamically insert assert statements during test development for rapid test creation and debugging.
9
10
```python { .api }
11
def insert_assert(value) -> int:
12
"""
13
Insert assert statement for testing (requires Python 3.8+).
14
15
Analyzes the calling code and generates an assert statement comparing
16
the provided value with its current runtime value. The generated assert
17
is formatted and can be written to the source file.
18
19
Parameters:
20
- value: Value to create assert statement for
21
22
Returns:
23
Number of insert_assert calls in the current test
24
25
Raises:
26
RuntimeError: If Python version < 3.8 or code inspection fails
27
"""
28
```
29
30
Usage examples:
31
32
```python
33
def test_calculation():
34
result = calculate_something(10, 20)
35
36
# During development, use insert_assert to capture expected values
37
insert_assert(result) # Generates: assert result == 42
38
39
# After running tests, the insert_assert calls are replaced with actual asserts
40
assert result == 42
41
42
def test_data_processing():
43
data = process_data([1, 2, 3, 4, 5])
44
45
# Multiple insert_assert calls in one test
46
insert_assert(len(data)) # Generates: assert len(data) == 5
47
insert_assert(data[0]) # Generates: assert data[0] == 2
48
insert_assert(sum(data)) # Generates: assert sum(data) == 15
49
50
# After processing:
51
assert len(data) == 5
52
assert data[0] == 2
53
assert sum(data) == 15
54
55
def test_complex_object():
56
obj = create_complex_object()
57
58
# Works with complex expressions
59
insert_assert(obj.property.method())
60
# Generates: assert obj.property.method() == expected_value
61
```
62
63
### Pytest Configuration Options
64
65
Command-line options for controlling insert_assert behavior.
66
67
```bash
68
# Print generated assert statements instead of writing to files
69
pytest --insert-assert-print
70
71
# Fail tests that contain insert_assert calls (for CI/CD)
72
pytest --insert-assert-fail
73
```
74
75
### Pytest Fixtures
76
77
Built-in fixtures for accessing insert_assert functionality and integration.
78
79
```python { .api }
80
@pytest.fixture(name='insert_assert')
81
def insert_assert_fixture() -> Callable[[Any], int]:
82
"""
83
Pytest fixture providing access to insert_assert function.
84
85
Returns:
86
insert_assert function for use in test functions
87
"""
88
89
@pytest.fixture(scope='session', autouse=True)
90
def insert_assert_add_to_builtins() -> None:
91
"""
92
Automatically add insert_assert and debug to builtins for all tests.
93
94
Makes insert_assert and debug available without imports in test files.
95
"""
96
97
@pytest.fixture(autouse=True)
98
def insert_assert_maybe_fail(pytestconfig: pytest.Config) -> Generator[None, None, None]:
99
"""
100
Auto-use fixture that optionally fails tests containing insert_assert calls.
101
102
Controlled by --insert-assert-fail command line option.
103
"""
104
105
@pytest.fixture(scope='session', autouse=True)
106
def insert_assert_session(pytestconfig: pytest.Config) -> Generator[None, None, None]:
107
"""
108
Session-level fixture handling insert_assert code generation and file writing.
109
110
Manages the actual insertion of assert statements into source files.
111
"""
112
```
113
114
Usage with fixtures:
115
116
```python
117
def test_with_fixture(insert_assert):
118
# Use insert_assert fixture explicitly
119
result = some_calculation()
120
count = insert_assert(result)
121
assert count == 1 # First insert_assert call in this test
122
123
def test_builtin_access():
124
# insert_assert available without imports due to auto-fixture
125
data = get_test_data()
126
insert_assert(data['status']) # Works without explicit import
127
128
# debug also available
129
debug(data) # Works without explicit import
130
```
131
132
### Pytest Hooks
133
134
Plugin hooks for customizing test behavior and reporting.
135
136
```python { .api }
137
def pytest_addoption(parser) -> None:
138
"""
139
Add command-line options for insert_assert functionality.
140
141
Adds:
142
- --insert-assert-print: Print statements instead of writing files
143
- --insert-assert-fail: Fail tests with insert_assert calls
144
"""
145
146
def pytest_report_teststatus(report: pytest.TestReport, config: pytest.Config):
147
"""
148
Custom test status reporting for insert_assert failures.
149
150
Shows 'INSERT ASSERT' status for tests failed due to insert_assert calls.
151
152
Returns:
153
Tuple of (outcome, letter, verbose) for insert_assert failures
154
"""
155
156
def pytest_terminal_summary() -> None:
157
"""
158
Print summary of insert_assert operations at end of test session.
159
160
Shows statistics about generated assert statements and file modifications.
161
"""
162
```
163
164
### Development Workflow Integration
165
166
Typical workflow for using insert_assert in test development:
167
168
```python
169
# 1. Initial test development
170
def test_new_feature():
171
result = new_feature_function()
172
173
# Use insert_assert to capture actual values during development
174
insert_assert(result.status)
175
insert_assert(result.data)
176
insert_assert(len(result.items))
177
178
# 2. Run tests with print mode to see generated asserts
179
# pytest test_file.py --insert-assert-print
180
181
# 3. Review printed assert statements:
182
# --------------------------------------------------------------------------------
183
# test_file.py - 5:
184
# --------------------------------------------------------------------------------
185
# # insert_assert(result.status)
186
# assert result.status == 'success'
187
# --------------------------------------------------------------------------------
188
189
# 4. Run tests normally to write asserts to file
190
# pytest test_file.py
191
192
# 5. Final test after insert_assert replacement:
193
def test_new_feature():
194
result = new_feature_function()
195
196
# insert_assert calls replaced with actual assertions
197
assert result.status == 'success'
198
assert result.data == {'key': 'value'}
199
assert len(result.items) == 3
200
```
201
202
### Error Handling and Edge Cases
203
204
The plugin handles various edge cases gracefully:
205
206
```python
207
def test_error_handling():
208
# Works with complex expressions
209
obj = get_complex_object()
210
insert_assert(obj.nested.property[0].method())
211
212
# Handles exceptions during formatting
213
problematic_value = get_problematic_value()
214
insert_assert(problematic_value) # Won't crash even if repr() fails
215
216
# Works with custom objects
217
custom = CustomClass()
218
insert_assert(custom) # Uses appropriate representation
219
220
def test_multiple_calls_same_line():
221
x, y = 1, 2
222
# Multiple insert_assert calls - handles duplicates
223
insert_assert(x); insert_assert(y)
224
# Second call on same line will be skipped in file writing
225
```
226
227
### Integration with Devtools Debug
228
229
Seamless integration with devtools debug functionality:
230
231
```python
232
def test_with_debug():
233
# Both debug and insert_assert available in test environment
234
data = process_test_data()
235
236
# Debug for inspection during development
237
debug(data)
238
239
# insert_assert for generating test assertions
240
insert_assert(data['result'])
241
insert_assert(data['error_count'])
242
243
def test_debug_in_fixtures(insert_assert):
244
# Debug works in pytest fixtures and test functions
245
@pytest.fixture
246
def test_data():
247
data = create_test_data()
248
debug(data) # Available without import
249
return data
250
```
251
252
### File Modification and Code Generation
253
254
The plugin automatically handles source file modification:
255
256
- **Black Integration**: Generated code is formatted using Black configuration from `pyproject.toml`
257
- **Duplicate Handling**: Prevents multiple asserts on the same line
258
- **Reversible Operation**: Original `insert_assert` calls are replaced, not deleted
259
- **Safe Writing**: Files are only modified when tests pass
260
261
```python
262
# Configuration via pyproject.toml affects generated code formatting
263
[tool.black]
264
line-length = 88
265
target-version = ['py38']
266
267
# Generated assert statements respect Black configuration:
268
# assert very_long_variable_name_here == {
269
# "key1": "value1",
270
# "key2": "value2"
271
# }
272
```
273
274
### Testing Insert Assert Itself
275
276
For testing the insert_assert functionality:
277
278
```python
279
def test_insert_assert_behavior():
280
# Test that insert_assert returns correct count
281
count1 = insert_assert(42)
282
count2 = insert_assert("hello")
283
284
assert count1 == 1
285
assert count2 == 2
286
287
def test_insert_assert_with_expressions():
288
data = [1, 2, 3]
289
290
# Test with expressions
291
insert_assert(len(data))
292
insert_assert(sum(data))
293
insert_assert(data[0])
294
295
# These will generate appropriate assert statements
296
```
297
298
## Types
299
300
```python { .api }
301
@dataclass
302
class ToReplace:
303
"""
304
Represents a code replacement operation for insert_assert.
305
"""
306
file: Path # Source file to modify
307
start_line: int # Starting line number
308
end_line: int | None # Ending line number
309
code: str # Replacement code
310
311
class PlainRepr(str):
312
"""
313
String class where repr() doesn't include quotes.
314
Used for custom representation in generated assert statements.
315
"""
316
def __repr__(self) -> str: ...
317
318
# Context variables for tracking insert_assert state
319
insert_assert_calls: ContextVar[int] # Count of calls in current test
320
insert_assert_summary: ContextVar[list[str]] # Session summary information
321
322
# Global state
323
to_replace: list[ToReplace] # List of pending code replacements
324
```