0
# Aspect-Oriented Benchmarking
1
2
## Overview
3
4
Aspect-oriented benchmarking allows you to benchmark existing functions without modifying the test code structure. This is useful for benchmarking third-party libraries, existing functions, or when you want to benchmark multiple function calls within a single test.
5
6
## Core APIs
7
8
### BenchmarkFixture.weave
9
10
```python { .api }
11
def weave(self, target, **kwargs) -> None:
12
"""
13
Apply benchmarking to a target function using aspect-oriented programming.
14
15
Args:
16
target: The function, method, or object to benchmark
17
**kwargs: Additional arguments passed to aspectlib.weave()
18
19
Raises:
20
ImportError: If aspectlib is not installed
21
FixtureAlreadyUsed: If the fixture has already been used in this test
22
"""
23
```
24
25
### BenchmarkFixture.patch
26
27
```python { .api }
28
def patch(self, target, **kwargs) -> None:
29
"""
30
Alias for weave method - applies benchmarking to target function.
31
32
Args:
33
target: The function, method, or object to benchmark
34
**kwargs: Additional arguments passed to aspectlib.weave()
35
36
Raises:
37
ImportError: If aspectlib is not installed
38
FixtureAlreadyUsed: If the fixture has already been used in this test
39
"""
40
```
41
42
### benchmark_weave fixture
43
44
```python { .api }
45
@pytest.fixture
46
def benchmark_weave(benchmark) -> callable:
47
"""
48
Shortcut fixture that provides direct access to benchmark.weave.
49
50
Returns:
51
callable: The benchmark.weave method
52
"""
53
```
54
55
## Installation Requirements
56
57
Aspect-oriented benchmarking requires the `aspectlib` package:
58
59
```bash
60
pip install pytest-benchmark[aspect]
61
# or
62
pip install aspectlib
63
```
64
65
## Usage Examples
66
67
### Basic Function Weaving
68
69
```python
70
import math
71
72
def test_math_operations(benchmark):
73
# Benchmark all calls to math.sqrt in this test
74
benchmark.weave(math.sqrt)
75
76
# Now all calls to math.sqrt will be benchmarked
77
result1 = math.sqrt(16)
78
result2 = math.sqrt(25)
79
result3 = math.sqrt(36)
80
81
assert result1 == 4.0
82
assert result2 == 5.0
83
assert result3 == 6.0
84
```
85
86
### Benchmarking Class Methods
87
88
```python
89
class DataProcessor:
90
def process(self, data):
91
return sum(x**2 for x in data)
92
93
def transform(self, data):
94
return [x * 2 for x in data]
95
96
def test_class_method_weaving(benchmark):
97
processor = DataProcessor()
98
99
# Benchmark the process method
100
benchmark.weave(processor, 'process')
101
102
data = list(range(1000))
103
result = processor.process(data)
104
105
assert result == sum(x**2 for x in range(1000))
106
```
107
108
### Using benchmark_weave Fixture
109
110
```python
111
def test_with_weave_fixture(benchmark_weave):
112
import json
113
114
# Direct access to weave functionality
115
benchmark_weave(json.dumps)
116
117
data = {"key": "value", "numbers": [1, 2, 3, 4, 5]}
118
result = json.dumps(data)
119
120
assert '"key": "value"' in result
121
```
122
123
### Weaving Multiple Functions
124
125
```python
126
def custom_function(x):
127
return x * 2 + 1
128
129
def another_function(x, y):
130
return x + y
131
132
def test_multiple_weaving(benchmark):
133
# Note: Only one weave per fixture instance
134
# This will benchmark the first function called
135
benchmark.weave(custom_function)
136
137
result1 = custom_function(5) # This gets benchmarked
138
result2 = another_function(3, 4) # This doesn't
139
140
assert result1 == 11
141
assert result2 == 7
142
```
143
144
### Advanced Weaving with aspectlib Options
145
146
```python
147
def test_advanced_weaving(benchmark):
148
def target_function(a, b, c=10):
149
return a + b + c
150
151
# Pass additional options to aspectlib.weave
152
benchmark.weave(
153
target_function,
154
# aspectlib options
155
lazy=True, # Lazy weaving
156
# methods=['__call__'] # Specific methods to weave
157
)
158
159
result = target_function(1, 2, c=3)
160
assert result == 6
161
```
162
163
## Weaving Patterns
164
165
### Library Function Benchmarking
166
167
```python
168
import hashlib
169
170
def test_hashlib_performance(benchmark):
171
benchmark.weave(hashlib.md5)
172
173
data = b"Hello, World!" * 1000
174
175
# All md5() calls in this test will be benchmarked
176
hash1 = hashlib.md5(data)
177
hash2 = hashlib.md5(data)
178
179
assert hash1.hexdigest() == hash2.hexdigest()
180
```
181
182
### Method Interception
183
184
```python
185
class DatabaseConnection:
186
def __init__(self):
187
self.queries = []
188
189
def execute(self, query):
190
self.queries.append(query)
191
return f"Result for: {query}"
192
193
def test_database_method_weaving(benchmark):
194
db = DatabaseConnection()
195
196
# Benchmark the execute method
197
benchmark.weave(db.execute)
198
199
# Multiple calls - all benchmarked as one aggregate
200
results = []
201
results.append(db.execute("SELECT * FROM users"))
202
results.append(db.execute("SELECT * FROM products"))
203
results.append(db.execute("SELECT * FROM orders"))
204
205
assert len(results) == 3
206
assert len(db.queries) == 3
207
```
208
209
### Module-Level Function Weaving
210
211
```python
212
# mymodule.py content (example)
213
def expensive_computation(n):
214
return sum(i**2 for i in range(n))
215
216
# Test file
217
def test_module_function_weaving(benchmark):
218
import mymodule
219
220
# Weave the module function
221
benchmark.weave(mymodule.expensive_computation)
222
223
result = mymodule.expensive_computation(1000)
224
expected = sum(i**2 for i in range(1000))
225
assert result == expected
226
```
227
228
## Limitations and Considerations
229
230
### Single Use Constraint
231
232
```python
233
def test_weave_single_use(benchmark):
234
def func1():
235
return 1
236
237
def func2():
238
return 2
239
240
# This works
241
benchmark.weave(func1)
242
result1 = func1()
243
244
# This would raise FixtureAlreadyUsed
245
# benchmark.weave(func2) # Error!
246
```
247
248
### Cleanup Behavior
249
250
The weaving is automatically cleaned up after the test completes. No manual cleanup is required.
251
252
```python
253
def test_automatic_cleanup(benchmark):
254
import math
255
256
original_sqrt = math.sqrt
257
benchmark.weave(math.sqrt)
258
259
# Function is woven during test
260
result = math.sqrt(16)
261
assert result == 4.0
262
263
# After test completes, weaving is automatically removed
264
```
265
266
### Error Handling
267
268
```python
269
def test_weave_error_handling(benchmark):
270
def failing_function():
271
raise ValueError("Test error")
272
273
benchmark.weave(failing_function)
274
275
# Exceptions are properly propagated
276
with pytest.raises(ValueError):
277
failing_function()
278
279
# Benchmark still captures error state
280
assert benchmark.has_error
281
```
282
283
## Integration with Test Frameworks
284
285
### Compatibility with Mock Libraries
286
287
```python
288
from unittest.mock import patch, MagicMock
289
290
def test_weave_with_mocks(benchmark):
291
with patch('requests.get') as mock_get:
292
mock_get.return_value = MagicMock(status_code=200)
293
294
# Can weave mocked functions
295
benchmark.weave(mock_get)
296
297
import requests
298
response = requests.get('http://example.com')
299
300
assert response.status_code == 200
301
mock_get.assert_called_once()
302
```
303
304
### Fixture Interaction
305
306
```python
307
@pytest.fixture
308
def sample_data():
309
return list(range(1000))
310
311
def test_weave_with_fixtures(benchmark, sample_data):
312
def process_data(data):
313
return sum(x**2 for x in data)
314
315
benchmark.weave(process_data)
316
317
result = process_data(sample_data)
318
expected = sum(x**2 for x in range(1000))
319
assert result == expected
320
```