0
# Test Tools and Decorators
1
2
Utilities for writing parameterized tests, BDD-style specifications, and enhanced test setup/teardown functionality. These tools extend unittest capabilities with modern testing patterns.
3
4
## Capabilities
5
6
### Parameterized Testing
7
8
Decorators for creating parameterized tests that run with multiple input values.
9
10
```python { .api }
11
def params(*paramList):
12
"""
13
Make a test function or method parameterized by parameters.
14
15
Parameters may be defined as simple values or tuples. To pass a tuple
16
as a simple value, wrap it in another tuple.
17
18
Parameters:
19
- paramList: Variable arguments, each becomes a test parameter set
20
21
Returns:
22
Decorator function that adds paramList attribute to test function
23
24
Example:
25
@params(1, 2, 3)
26
def test_nums(num):
27
assert num < 4
28
29
@params((1, 2), (2, 3), (4, 5))
30
def test_less_than(a, b):
31
assert a < b
32
"""
33
34
def cartesian_params(*paramList):
35
"""
36
Make a test function or method parameterized by cartesian product of parameters.
37
38
Parameters in the list must be defined as iterable objects (tuple or list).
39
40
Parameters:
41
- paramList: Variable arguments of iterables for cartesian product
42
43
Returns:
44
Decorator function that adds paramList attribute to test function
45
46
Example:
47
@cartesian_params((1, 2, 3), ('a', 'b'))
48
def test_nums(num, char):
49
assert num < ord(char)
50
"""
51
```
52
53
### Test Setup and Teardown Decorators
54
55
Decorators for adding setup and teardown methods to function-based tests.
56
57
```python { .api }
58
def with_setup(setup):
59
"""
60
Decorator that sets the setup method to be executed before the test.
61
62
Currently works only for function test cases.
63
64
Parameters:
65
- setup: The method to be executed before the test
66
67
Returns:
68
Decorator function that adds setup attribute to test function
69
70
Example:
71
def my_setup():
72
# setup code
73
pass
74
75
@with_setup(my_setup)
76
def test_something():
77
# test code
78
pass
79
"""
80
81
def with_teardown(teardown):
82
"""
83
Decorator that sets the teardown method to be executed after the test.
84
85
Currently works only for function test cases.
86
87
Parameters:
88
- teardown: The method to be executed after the test
89
90
Returns:
91
Decorator function that adds tearDownFunc attribute to test function
92
93
Example:
94
def my_teardown():
95
# teardown code
96
pass
97
98
@with_teardown(my_teardown)
99
def test_something():
100
# test code
101
pass
102
"""
103
```
104
105
### BDD-Style Testing (such)
106
107
Domain-specific language for writing behavior-driven tests with hierarchical organization and fixtures.
108
109
```python { .api }
110
def A(description):
111
"""
112
Test scenario context manager.
113
114
Returns a Scenario instance, conventionally bound to 'it'.
115
116
Parameters:
117
- description: Description of the test scenario
118
119
Returns:
120
Context manager yielding Scenario instance
121
122
Example:
123
with such.A('calculator functionality') as it:
124
# tests and fixtures
125
pass
126
"""
127
128
class Scenario:
129
"""
130
A test scenario that defines a set of fixtures and tests.
131
"""
132
133
def having(self, description):
134
"""
135
Define a new group under the current group.
136
137
Context manager for creating nested test groups. Fixtures and tests
138
defined within the block belong to the new group.
139
140
Parameters:
141
- description: Description of the test group
142
143
Returns:
144
Context manager yielding self
145
146
Example:
147
with it.having('valid input data'):
148
# nested tests and fixtures
149
pass
150
"""
151
152
def uses(self, layer):
153
"""
154
Add a layer as mixin to this group.
155
156
Parameters:
157
- layer: Layer class to add as mixin
158
"""
159
160
def has_setup(self, func):
161
"""
162
Add a setup method to this group.
163
164
The setup method runs once before any tests in the containing group.
165
A group may define multiple setup functions - they execute in order.
166
167
Parameters:
168
- func: Setup function to add
169
170
Returns:
171
The original function (decorator pattern)
172
173
Example:
174
@it.has_setup
175
def setup():
176
it.data = load_test_data()
177
"""
178
179
def has_teardown(self, func):
180
"""
181
Add a teardown method to this group.
182
183
The teardown method runs once after all tests in the containing group.
184
A group may define multiple teardown functions - they execute in order.
185
186
Parameters:
187
- func: Teardown function to add
188
189
Returns:
190
The original function (decorator pattern)
191
192
Example:
193
@it.has_teardown
194
def teardown():
195
cleanup_test_data()
196
"""
197
198
def has_test_setup(self, func):
199
"""
200
Add a test case setup method to this group.
201
202
The setup method runs before each test in the containing group.
203
Functions may optionally take one argument (the TestCase instance).
204
205
Parameters:
206
- func: Test setup function to add
207
208
Example:
209
@it.has_test_setup
210
def setup(case):
211
case.client = TestClient()
212
"""
213
214
def has_test_teardown(self, func):
215
"""
216
Add a test case teardown method to this group.
217
218
The teardown method runs after each test in the containing group.
219
Functions may optionally take one argument (the TestCase instance).
220
221
Parameters:
222
- func: Test teardown function to add
223
224
Example:
225
@it.has_test_teardown
226
def teardown(case):
227
case.client.close()
228
"""
229
230
def should(self, desc):
231
"""
232
Define a test case.
233
234
Each function marked with this decorator becomes a test case in the
235
current group. Test functions may optionally take one argument
236
(the TestCase instance) for executing assert methods.
237
238
Parameters:
239
- desc: Description of what the test should do, or the test function
240
if used without arguments
241
242
Returns:
243
Decorator function or Case instance if desc is a function
244
245
Example:
246
@it.should('calculate sum correctly')
247
def test_sum(case):
248
result = calculator.add(2, 3)
249
case.assertEqual(result, 5)
250
251
@it.should
252
def test_subtraction():
253
\"\"\"subtract numbers correctly\"\"\"
254
assert calculator.subtract(5, 3) == 2
255
"""
256
257
def createTests(self, mod):
258
"""
259
Generate test cases for this scenario.
260
261
You must call this with globals() to generate tests from the scenario.
262
If you don't call this, no tests will be created.
263
264
Parameters:
265
- mod: Module globals dictionary (usually globals())
266
267
Example:
268
it.createTests(globals())
269
"""
270
271
class Group:
272
"""A group of tests with common fixtures and description."""
273
274
def __init__(self, description, indent=0, parent=None, base_layer=None):
275
"""
276
Initialize test group.
277
278
Parameters:
279
- description: Group description
280
- indent: Indentation level
281
- parent: Parent group
282
- base_layer: Base layer class
283
"""
284
285
def addCase(self, case):
286
"""Add a test case to this group."""
287
288
def addSetup(self, func):
289
"""Add a setup function to this group."""
290
291
def addTeardown(self, func):
292
"""Add a teardown function to this group."""
293
294
def child(self, description, base_layer=None):
295
"""Create a child group."""
296
297
class Case:
298
"""Information about a test case."""
299
300
def __init__(self, group, func, description):
301
"""
302
Initialize test case.
303
304
Parameters:
305
- group: Parent group
306
- func: Test function
307
- description: Test description
308
"""
309
310
def __call__(self, testcase, *args):
311
"""Execute the test case with given TestCase instance."""
312
```
313
314
## Usage Examples
315
316
### Parameterized Tests
317
318
```python
319
from nose2.tools import params, cartesian_params
320
321
# Simple parameter list
322
@params(1, 2, 3, 4, 5)
323
def test_positive_numbers(num):
324
assert num > 0
325
326
# Tuple parameters
327
@params((1, 2, 3), (4, 5, 9), (6, 7, 13))
328
def test_sum(a, b, expected):
329
assert a + b == expected
330
331
# Cartesian product parameters
332
@cartesian_params([1, 2], ['a', 'b'], [True, False])
333
def test_combinations(num, char, flag):
334
# Test runs with all combinations: 8 total tests
335
assert isinstance(num, int)
336
assert isinstance(char, str)
337
assert isinstance(flag, bool)
338
339
# Class method parameterization
340
class TestMath(unittest.TestCase):
341
342
@params(0, 1, 5, 10)
343
def test_square_root(self, num):
344
result = math.sqrt(num * num)
345
self.assertEqual(result, num)
346
```
347
348
### Setup and Teardown Decorators
349
350
```python
351
from nose2.tools.decorators import with_setup, with_teardown
352
353
# Global test data
354
test_data = {}
355
356
def setup_data():
357
test_data['value'] = 42
358
test_data['list'] = [1, 2, 3]
359
360
def cleanup_data():
361
test_data.clear()
362
363
@with_setup(setup_data)
364
@with_teardown(cleanup_data)
365
def test_data_access():
366
assert test_data['value'] == 42
367
assert len(test_data['list']) == 3
368
```
369
370
### BDD-Style Testing
371
372
```python
373
from nose2.tools import such
374
375
with such.A('web application') as it:
376
377
@it.has_setup
378
def setup():
379
it.app = create_test_app()
380
it.client = it.app.test_client()
381
382
@it.has_teardown
383
def teardown():
384
it.app.cleanup()
385
386
with it.having('user authentication'):
387
388
@it.has_test_setup
389
def setup_auth(case):
390
case.user = create_test_user()
391
392
@it.should('allow login with valid credentials')
393
def test_valid_login(case):
394
response = it.client.post('/login', data={
395
'username': case.user.username,
396
'password': 'password'
397
})
398
case.assertEqual(response.status_code, 200)
399
400
@it.should('reject invalid credentials')
401
def test_invalid_login(case):
402
response = it.client.post('/login', data={
403
'username': 'wrong',
404
'password': 'wrong'
405
})
406
case.assertEqual(response.status_code, 401)
407
408
with it.having('API endpoints'):
409
410
@it.should('return JSON responses')
411
def test_json_response(case):
412
response = it.client.get('/api/status')
413
case.assertEqual(response.content_type, 'application/json')
414
415
# Generate test cases - this is required!
416
it.createTests(globals())
417
```
418
419
### Mixed Testing Styles
420
421
```python
422
import unittest
423
from nose2.tools import params, such
424
from nose2.tools.decorators import with_setup
425
426
# Traditional unittest class
427
class TestBasicMath(unittest.TestCase):
428
429
@params(1, 2, 3, 5, 8)
430
def test_fibonacci_numbers(self, num):
431
self.assertIn(num, [1, 1, 2, 3, 5, 8, 13, 21])
432
433
# Function-based tests with setup
434
def setup_calculator():
435
global calc
436
calc = Calculator()
437
438
@with_setup(setup_calculator)
439
@params((2, 3, 5), (10, 5, 15), (0, 100, 100))
440
def test_addition(a, b, expected):
441
result = calc.add(a, b)
442
assert result == expected
443
444
# BDD-style specification
445
with such.A('calculator application') as it:
446
447
@it.has_setup
448
def setup():
449
it.calc = Calculator()
450
451
with it.having('basic arithmetic operations'):
452
453
@it.should('add positive numbers')
454
def test_positive_addition(case):
455
result = it.calc.add(2, 3)
456
case.assertEqual(result, 5)
457
458
@it.should('handle zero values')
459
def test_zero_addition(case):
460
case.assertEqual(it.calc.add(0, 5), 5)
461
case.assertEqual(it.calc.add(5, 0), 5)
462
463
it.createTests(globals())
464
```