Parameterized testing with any Python test framework
npx @tessl/cli install tessl/pypi-parameterized@0.9.00
# Parameterized
1
2
Parameterized testing with any Python test framework. This library provides comprehensive parameterized testing capabilities that eliminate repetitive test code by allowing developers to define test logic once and execute it across multiple parameter sets. It maintains compatibility with multiple test frameworks including nose, pytest, unittest, and unittest2.
3
4
## Package Information
5
6
- **Package Name**: parameterized
7
- **Language**: Python
8
- **Installation**: `pip install parameterized`
9
- **Requires Python**: >=3.7
10
11
## Core Imports
12
13
```python
14
from parameterized import parameterized, param, parameterized_class
15
```
16
17
Version information:
18
19
```python { .api }
20
__version__: str # Package version ("0.9.0")
21
```
22
23
## Basic Usage
24
25
```python
26
from parameterized import parameterized, param, parameterized_class
27
import unittest
28
import math
29
30
# Basic parameterized test function
31
@parameterized([
32
(2, 2, 4),
33
(2, 3, 8),
34
(1, 9, 1),
35
(0, 9, 0),
36
])
37
def test_pow(base, exponent, expected):
38
assert math.pow(base, exponent) == expected
39
40
# Parameterized test in unittest class
41
class TestMathUnitTest(unittest.TestCase):
42
@parameterized.expand([
43
("negative", -1.5, -2.0),
44
("integer", 1, 1.0),
45
("large fraction", 1.6, 1),
46
])
47
def test_floor(self, name, input, expected):
48
self.assertEqual(math.floor(input), expected)
49
50
# Parameterized test class
51
@parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [
52
(1, 2, 3, 2),
53
(3, 4, 7, 12),
54
])
55
class TestArithmetic(unittest.TestCase):
56
def test_sum(self):
57
self.assertEqual(self.a + self.b, self.expected_sum)
58
59
def test_product(self):
60
self.assertEqual(self.a * self.b, self.expected_product)
61
```
62
63
## Architecture
64
65
The parameterized library uses a decorator-based architecture that transforms test functions and classes at import time:
66
67
- **parameterized decorator**: Transforms functions into test generators for nose/pytest or standalone functions for unittest
68
- **param helper**: Represents individual parameter sets with positional and keyword arguments
69
- **Test runner detection**: Automatically detects the test framework and adapts behavior accordingly
70
- **Mock patch compatibility**: Maintains compatibility with mock.patch decorators through patch re-application
71
72
This design enables seamless integration with existing test suites while providing consistent parameterization across different test frameworks.
73
74
## Capabilities
75
76
### Function Parameterization
77
78
The core `@parameterized` decorator for creating parameterized test functions that work with nose, pytest, and standalone execution.
79
80
```python { .api }
81
class parameterized:
82
def __init__(self, input, doc_func=None, skip_on_empty=False):
83
"""
84
Initialize parameterized decorator.
85
86
Parameters:
87
- input: Iterable of parameter sets or callable returning iterable
88
- doc_func: Function to generate test documentation (func, num, param) -> str
89
- skip_on_empty: If True, skip test when input is empty; if False, raise ValueError
90
"""
91
92
@classmethod
93
def to_safe_name(cls, s):
94
"""
95
Convert string to safe name for test case naming.
96
97
Parameters:
98
- s: String to convert
99
100
Returns:
101
String with non-alphanumeric characters replaced by underscores
102
"""
103
```
104
105
### Class Method Parameterization
106
107
The `@parameterized.expand` class method for parameterizing test methods within unittest.TestCase subclasses.
108
109
```python { .api }
110
@classmethod
111
def expand(cls, input, name_func=None, doc_func=None, skip_on_empty=False, namespace=None, **legacy):
112
"""
113
Parameterize test methods in unittest.TestCase subclasses.
114
115
Parameters:
116
- input: Iterable of parameter sets or callable returning iterable
117
- name_func: Function to generate test method names (func, num, param) -> str
118
- doc_func: Function to generate test documentation (func, num, param) -> str
119
- skip_on_empty: If True, skip test when input is empty; if False, raise ValueError
120
- namespace: Namespace to inject test methods (defaults to caller's locals)
121
122
Returns:
123
Decorator function for test methods
124
"""
125
```
126
127
### Parameter Specification
128
129
The `param` class for specifying individual parameter sets with both positional and keyword arguments.
130
131
```python { .api }
132
class param:
133
def __new__(cls, *args, **kwargs):
134
"""
135
Create parameter set with positional and keyword arguments.
136
137
Parameters:
138
- *args: Positional arguments for test function
139
- **kwargs: Keyword arguments for test function
140
141
Returns:
142
param instance containing args and kwargs
143
"""
144
145
@classmethod
146
def explicit(cls, args=None, kwargs=None):
147
"""
148
Create param by explicitly specifying args and kwargs lists.
149
150
Parameters:
151
- args: Tuple/list of positional arguments
152
- kwargs: Dictionary of keyword arguments
153
154
Returns:
155
param instance
156
"""
157
158
@classmethod
159
def from_decorator(cls, args):
160
"""
161
Create param from decorator arguments with automatic type handling.
162
163
Parameters:
164
- args: Single value, tuple, or param instance
165
166
Returns:
167
param instance
168
"""
169
```
170
171
### Class Parameterization
172
173
Function for creating multiple parameterized test classes with different attribute values.
174
175
```python { .api }
176
def parameterized_class(attrs, input_values=None, class_name_func=None, classname_func=None):
177
"""
178
Parameterize test classes by setting attributes.
179
180
Parameters:
181
- attrs: String/list of attribute names or list of dicts with attribute values
182
- input_values: List of tuples with values for each attrs (when attrs is string/list)
183
- class_name_func: Function to generate class names (cls, idx, params_dict) -> str
184
- classname_func: Deprecated, use class_name_func instead
185
186
Returns:
187
Decorator function for test classes
188
"""
189
```
190
191
### Utility Functions
192
193
Helper functions for parameter processing, test naming, and framework compatibility.
194
195
```python { .api }
196
def parameterized_argument_value_pairs(func, p):
197
"""
198
Return tuples of parameterized arguments and their values.
199
200
Parameters:
201
- func: Test function to analyze
202
- p: param instance with arguments
203
204
Returns:
205
List of (arg_name, value) tuples
206
"""
207
208
def short_repr(x, n=64):
209
"""
210
Create shortened string representation guaranteed to be unicode.
211
212
Parameters:
213
- x: Object to represent
214
- n: Maximum length (default: 64)
215
216
Returns:
217
Unicode string representation
218
"""
219
220
def default_doc_func(func, num, p):
221
"""
222
Default function for generating test documentation.
223
224
Parameters:
225
- func: Test function
226
- num: Parameter set number
227
- p: param instance
228
229
Returns:
230
Documentation string or None
231
"""
232
233
def default_name_func(func, num, p):
234
"""
235
Default function for generating test names.
236
237
Parameters:
238
- func: Test function
239
- num: Parameter set number
240
- p: param instance
241
242
Returns:
243
Test name string
244
"""
245
246
def set_test_runner(name):
247
"""
248
Override automatic test runner detection.
249
250
Parameters:
251
- name: Test runner name ("unittest", "unittest2", "nose", "nose2", "pytest")
252
253
Raises:
254
TypeError: If runner name is invalid
255
"""
256
257
def detect_runner():
258
"""
259
Detect current test runner by examining the call stack.
260
261
Returns:
262
String name of detected test runner or None
263
"""
264
```
265
266
## Usage Examples
267
268
### Advanced Parameter Specification
269
270
```python
271
from parameterized import parameterized, param
272
273
@parameterized([
274
param(1, 2, expected=3),
275
param("hello", " world", expected="hello world"),
276
param([1, 2], [3, 4], expected=[1, 2, 3, 4]),
277
])
278
def test_operations(a, b, expected):
279
if isinstance(a, str):
280
result = a + b
281
elif isinstance(a, list):
282
result = a + b
283
else:
284
result = a + b
285
286
assert result == expected
287
```
288
289
### Custom Naming and Documentation
290
291
```python
292
from parameterized import parameterized
293
294
def custom_name_func(func, num, p):
295
return f"{func.__name__}_case_{num}_{p.args[0]}"
296
297
def custom_doc_func(func, num, p):
298
return f"Test case {num}: {func.__name__} with input {p.args[0]}"
299
300
@parameterized([
301
("positive",), ("negative",), ("zero",)
302
], name_func=custom_name_func, doc_func=custom_doc_func)
303
def test_number_type(number_type):
304
assert number_type in ["positive", "negative", "zero"]
305
```
306
307
### Mock Patch Compatibility
308
309
```python
310
from unittest.mock import patch
311
from parameterized import parameterized
312
313
class TestWithMocks(unittest.TestCase):
314
@parameterized.expand([
315
("user1", "data1"),
316
("user2", "data2"),
317
])
318
@patch('my_module.external_service')
319
def test_service_calls(self, username, expected_data, mock_service):
320
mock_service.get_data.return_value = expected_data
321
result = my_module.process_user(username)
322
mock_service.get_data.assert_called_once_with(username)
323
self.assertEqual(result, expected_data)
324
```
325
326
### Complex Class Parameterization
327
328
```python
329
from parameterized import parameterized_class
330
331
@parameterized_class([
332
{"database": "sqlite", "connection_string": ":memory:", "supports_transactions": True},
333
{"database": "mysql", "connection_string": "mysql://localhost/test", "supports_transactions": True},
334
{"database": "redis", "connection_string": "redis://localhost", "supports_transactions": False},
335
])
336
class TestDatabaseOperations(unittest.TestCase):
337
def setUp(self):
338
self.db = create_database_connection(self.database, self.connection_string)
339
340
def test_basic_operations(self):
341
self.db.insert("key", "value")
342
result = self.db.get("key")
343
self.assertEqual(result, "value")
344
345
def test_transaction_support(self):
346
if self.supports_transactions:
347
with self.db.transaction():
348
self.db.insert("key", "value")
349
else:
350
with self.assertRaises(NotImplementedError):
351
self.db.transaction()
352
```