unittest-parametrize 1.6.0__tar.gz → 1.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/CHANGELOG.rst +39 -1
- {unittest_parametrize-1.6.0/src/unittest_parametrize.egg-info → unittest_parametrize-1.8.0}/PKG-INFO +149 -40
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/README.rst +144 -37
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/pyproject.toml +49 -9
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/src/unittest_parametrize/__init__.py +45 -15
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0/src/unittest_parametrize.egg-info}/PKG-INFO +149 -40
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/LICENSE +0 -0
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/MANIFEST.in +0 -0
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/setup.cfg +0 -0
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/src/unittest_parametrize/py.typed +0 -0
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/src/unittest_parametrize.egg-info/SOURCES.txt +0 -0
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/src/unittest_parametrize.egg-info/dependency_links.txt +0 -0
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/src/unittest_parametrize.egg-info/requires.txt +0 -0
- {unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/src/unittest_parametrize.egg-info/top_level.txt +0 -0
@@ -2,10 +2,48 @@
|
|
2
2
|
Changelog
|
3
3
|
=========
|
4
4
|
|
5
|
+
1.8.0 (2025-09-09)
|
6
|
+
------------------
|
7
|
+
|
8
|
+
* Support Python 3.14.
|
9
|
+
|
10
|
+
* Add support for providing single parameter values without wrapping tuples, such as:
|
11
|
+
|
12
|
+
.. code-block:: python
|
13
|
+
|
14
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
15
|
+
|
16
|
+
|
17
|
+
class EqualTests(ParametrizedTestCase):
|
18
|
+
@parametrize(
|
19
|
+
"x",
|
20
|
+
[1, 2, 3],
|
21
|
+
)
|
22
|
+
def test_equal(self, x: int) -> None:
|
23
|
+
self.assertEqual(x, x)
|
24
|
+
|
25
|
+
Thanks to Jirka Borovec for the report in `Issue #146 <https://github.com/adamchainz/unittest-parametrize/issues/146>`__.
|
26
|
+
|
27
|
+
1.7.0 (2025-08-27)
|
28
|
+
------------------
|
29
|
+
|
30
|
+
* Add support for the ``ids`` argument being a callable.
|
31
|
+
The callable is called once per parameter value and can return a string for that value or ``None`` to use the default.
|
32
|
+
|
33
|
+
`PR #143 <https://github.com/adamchainz/unittest-parametrize/pull/143>`__.
|
34
|
+
|
35
|
+
* Fail for collisions between autogenerated IDs and user-specified IDs when mixing tuples and ``param`` instances.
|
36
|
+
|
37
|
+
`PR #143 <https://github.com/adamchainz/unittest-parametrize/pull/143>`__.
|
38
|
+
|
39
|
+
* Update the type hints to allow mixing tuples and ``param`` instances.
|
40
|
+
|
41
|
+
`PR #143 <https://github.com/adamchainz/unittest-parametrize/pull/143>`__.
|
42
|
+
|
5
43
|
1.6.0 (2025-01-06)
|
6
44
|
------------------
|
7
45
|
|
8
|
-
* Add
|
46
|
+
* Add support for asynchronous tests.
|
9
47
|
|
10
48
|
Thanks to Adrien Cossa in `PR #121 <https://github.com/adamchainz/unittest-parametrize/pull/121>`__.
|
11
49
|
|
{unittest_parametrize-1.6.0/src/unittest_parametrize.egg-info → unittest_parametrize-1.8.0}/PKG-INFO
RENAMED
@@ -1,15 +1,15 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: unittest-parametrize
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.8.0
|
4
4
|
Summary: Parametrize tests within unittest TestCases.
|
5
5
|
Author-email: Adam Johnson <me@adamj.eu>
|
6
|
+
License-Expression: MIT
|
6
7
|
Project-URL: Changelog, https://github.com/adamchainz/unittest-parametrize/blob/main/CHANGELOG.rst
|
7
8
|
Project-URL: Funding, https://adamj.eu/books/
|
8
9
|
Project-URL: Repository, https://github.com/adamchainz/unittest-parametrize
|
9
10
|
Keywords: unittest
|
10
11
|
Classifier: Development Status :: 5 - Production/Stable
|
11
12
|
Classifier: Intended Audience :: Developers
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
13
13
|
Classifier: Natural Language :: English
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
15
15
|
Classifier: Programming Language :: Python :: 3.9
|
@@ -17,11 +17,13 @@ Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
20
21
|
Classifier: Typing :: Typed
|
21
22
|
Requires-Python: >=3.9
|
22
23
|
Description-Content-Type: text/x-rst
|
23
24
|
License-File: LICENSE
|
24
25
|
Requires-Dist: typing-extensions; python_version < "3.10"
|
26
|
+
Dynamic: license-file
|
25
27
|
|
26
28
|
====================
|
27
29
|
unittest-parametrize
|
@@ -58,7 +60,7 @@ Install with:
|
|
58
60
|
|
59
61
|
python -m pip install unittest-parametrize
|
60
62
|
|
61
|
-
Python 3.9 to 3.
|
63
|
+
Python 3.9 to 3.14 supported.
|
62
64
|
|
63
65
|
Usage
|
64
66
|
=====
|
@@ -76,15 +78,14 @@ There are two steps to parametrize a test case:
|
|
76
78
|
2. Apply ``@parametrize`` to any test methods for parametrization.
|
77
79
|
This decorator takes (at least):
|
78
80
|
|
79
|
-
* the argument names to parametrize, as comma-separated string
|
80
|
-
* a list of
|
81
|
+
* the argument names to parametrize, as comma-separated string or sequence of strings.
|
82
|
+
* a list of parameters to create individual tests for, which may be tuples, ``param`` objects, or single values (for one argument).
|
81
83
|
|
82
84
|
Here’s a basic example:
|
83
85
|
|
84
86
|
.. code-block:: python
|
85
87
|
|
86
|
-
from unittest_parametrize import parametrize
|
87
|
-
from unittest_parametrize import ParametrizedTestCase
|
88
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
88
89
|
|
89
90
|
|
90
91
|
class SquareTests(ParametrizedTestCase):
|
@@ -106,6 +107,25 @@ It supports both synchronous and asynchronous test methods.
|
|
106
107
|
.. |__init_subclass__ hook| replace:: ``__init_subclass__`` hook
|
107
108
|
__ https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__
|
108
109
|
|
110
|
+
Provide a single parameter without a wrapping tuple
|
111
|
+
---------------------------------------------------
|
112
|
+
|
113
|
+
If you only need a single parameter, you can provide values without wrapping them in tuples:
|
114
|
+
|
115
|
+
.. code-block:: python
|
116
|
+
|
117
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
118
|
+
|
119
|
+
|
120
|
+
class EqualTests(ParametrizedTestCase):
|
121
|
+
@parametrize(
|
122
|
+
"x",
|
123
|
+
[1, 2, 3],
|
124
|
+
)
|
125
|
+
def test_equal(self, x: int) -> None:
|
126
|
+
self.assertEqual(x, x)
|
127
|
+
|
128
|
+
|
109
129
|
Provide argument names as separate strings
|
110
130
|
------------------------------------------
|
111
131
|
|
@@ -113,8 +133,7 @@ You can provide argument names as a sequence of strings instead:
|
|
113
133
|
|
114
134
|
.. code-block:: python
|
115
135
|
|
116
|
-
from unittest_parametrize import parametrize
|
117
|
-
from unittest_parametrize import ParametrizedTestCase
|
136
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
118
137
|
|
119
138
|
|
120
139
|
class SquareTests(ParametrizedTestCase):
|
@@ -177,13 +196,20 @@ You can see these names when running the tests:
|
|
177
196
|
|
178
197
|
OK
|
179
198
|
|
180
|
-
You can customize these names
|
199
|
+
You can customize these names in several ways:
|
200
|
+
|
201
|
+
1. Using ``param`` objects with IDs.
|
202
|
+
2. Passing a sequence of strings as the ``ids`` argument.
|
203
|
+
3. Passing a callable as the ``ids`` argument.
|
204
|
+
|
205
|
+
Passing ``param`` objects with IDs
|
206
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
207
|
+
|
208
|
+
Pass a ``param`` object for each parameter set, setting the test ID suffix with the optional ``id`` argument:
|
181
209
|
|
182
210
|
.. code-block:: python
|
183
211
|
|
184
|
-
from unittest_parametrize import param
|
185
|
-
from unittest_parametrize import parametrize
|
186
|
-
from unittest_parametrize import ParametrizedTestCase
|
212
|
+
from unittest_parametrize import ParametrizedTestCase, param, parametrize
|
187
213
|
|
188
214
|
|
189
215
|
class SquareTests(ParametrizedTestCase):
|
@@ -197,7 +223,7 @@ You can customize these names by passing ``param`` objects, which contain the ar
|
|
197
223
|
def test_square(self, x: int, expected: int) -> None:
|
198
224
|
self.assertEqual(x**2, expected)
|
199
225
|
|
200
|
-
Yielding
|
226
|
+
Yielding more natural names:
|
201
227
|
|
202
228
|
.. code-block:: console
|
203
229
|
|
@@ -216,9 +242,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
|
|
216
242
|
|
217
243
|
.. code-block:: python
|
218
244
|
|
219
|
-
from unittest_parametrize import param
|
220
|
-
from unittest_parametrize import parametrize
|
221
|
-
from unittest_parametrize import ParametrizedTestCase
|
245
|
+
from unittest_parametrize import ParametrizedTestCase, param, parametrize
|
222
246
|
|
223
247
|
|
224
248
|
class SquareTests(ParametrizedTestCase):
|
@@ -232,7 +256,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
|
|
232
256
|
def test_square(self, x: int, expected: int) -> None:
|
233
257
|
self.assertEqual(x**2, expected)
|
234
258
|
|
235
|
-
ID-free ``param``\s fall back to the default index suffixes:
|
259
|
+
The ID-free ``param``\s fall back to the default index suffixes:
|
236
260
|
|
237
261
|
.. code-block:: console
|
238
262
|
|
@@ -245,12 +269,14 @@ ID-free ``param``\s fall back to the default index suffixes:
|
|
245
269
|
|
246
270
|
OK
|
247
271
|
|
248
|
-
|
272
|
+
Passing a sequence of strings as the ``ids`` argument
|
273
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
274
|
+
|
275
|
+
Another option is to provide the IDs in the separate ``ids`` argument:
|
249
276
|
|
250
277
|
.. code-block:: python
|
251
278
|
|
252
|
-
from unittest_parametrize import parametrize
|
253
|
-
from unittest_parametrize import ParametrizedTestCase
|
279
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
254
280
|
|
255
281
|
|
256
282
|
class SquareTests(ParametrizedTestCase):
|
@@ -265,6 +291,64 @@ Alternatively, you can provide the id’s separately with the ``ids`` argument:
|
|
265
291
|
def test_square(self, x: int, expected: int) -> None:
|
266
292
|
self.assertEqual(x**2, expected)
|
267
293
|
|
294
|
+
This option sets the full suffixes to the provided strings:
|
295
|
+
|
296
|
+
.. code-block:: console
|
297
|
+
|
298
|
+
$ python -m unittest t.py -v
|
299
|
+
test_square_one (example.SquareTests.test_square_one) ... ok
|
300
|
+
test_square_two (example.SquareTests.test_square_two) ... ok
|
301
|
+
|
302
|
+
----------------------------------------------------------------------
|
303
|
+
Ran 2 tests in 0.000s
|
304
|
+
|
305
|
+
OK
|
306
|
+
|
307
|
+
Passing a callable as the ``ids`` argument
|
308
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
309
|
+
|
310
|
+
The ``ids`` argument can also be a callable, which unittest-parametrize calls once per parameter value.
|
311
|
+
The callable can return a string for that value, or ``None`` to use the default index suffix.
|
312
|
+
The values are then joined with underscores to form the full suffix.
|
313
|
+
|
314
|
+
For example:
|
315
|
+
|
316
|
+
.. code-block:: python
|
317
|
+
|
318
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
319
|
+
|
320
|
+
|
321
|
+
def make_id(value):
|
322
|
+
if isinstance(value, int):
|
323
|
+
return f"num{value}"
|
324
|
+
return None
|
325
|
+
|
326
|
+
|
327
|
+
class SquareTests(ParametrizedTestCase):
|
328
|
+
@parametrize(
|
329
|
+
"x,expected",
|
330
|
+
[
|
331
|
+
(1, 1),
|
332
|
+
(2, 4),
|
333
|
+
],
|
334
|
+
ids=make_id,
|
335
|
+
)
|
336
|
+
def test_square(self, x: int, expected: int) -> None:
|
337
|
+
self.assertEqual(x**2, expected)
|
338
|
+
|
339
|
+
…yields:
|
340
|
+
|
341
|
+
.. code-block:: console
|
342
|
+
|
343
|
+
$ python -m unittest t.py -v
|
344
|
+
test_square_num1_num1 (example.SquareTests.test_square_num1_num1) ... ok
|
345
|
+
test_square_num2_num4 (example.SquareTests.test_square_num2_num4) ... ok
|
346
|
+
|
347
|
+
----------------------------------------------------------------------
|
348
|
+
Ran 2 tests in 0.000s
|
349
|
+
|
350
|
+
OK
|
351
|
+
|
268
352
|
Use with other test decorators
|
269
353
|
------------------------------
|
270
354
|
|
@@ -275,8 +359,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
|
|
275
359
|
.. code-block:: python
|
276
360
|
|
277
361
|
from unittest import mock
|
278
|
-
from unittest_parametrize import parametrize
|
279
|
-
from unittest_parametrize import ParametrizedTestCase
|
362
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
280
363
|
|
281
364
|
|
282
365
|
class CarpentryTests(ParametrizedTestCase):
|
@@ -285,8 +368,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
|
|
285
368
|
[(11,), (17,)],
|
286
369
|
)
|
287
370
|
@mock.patch("example.hammer", autospec=True)
|
288
|
-
def test_nail_a_board(self, mock_hammer, nails):
|
289
|
-
...
|
371
|
+
def test_nail_a_board(self, mock_hammer, nails): ...
|
290
372
|
|
291
373
|
Also note that due to how ``mock.patch`` always adds positional arguments at the start, the parametrized arguments must come last.
|
292
374
|
``@parametrize`` always adds parameters as keyword arguments, so you can also use `keyword-only syntax <https://peps.python.org/pep-3102/>`__ for parametrized arguments:
|
@@ -294,8 +376,7 @@ Also note that due to how ``mock.patch`` always adds positional arguments at the
|
|
294
376
|
.. code-block:: python
|
295
377
|
|
296
378
|
# ...
|
297
|
-
def test_nail_a_board(self, mock_hammer, *, nails):
|
298
|
-
...
|
379
|
+
def test_nail_a_board(self, mock_hammer, *, nails): ...
|
299
380
|
|
300
381
|
Multiple ``@parametrize`` decorators
|
301
382
|
------------------------------------
|
@@ -305,8 +386,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
|
|
305
386
|
|
306
387
|
.. code-block:: python
|
307
388
|
|
308
|
-
from unittest_parametrize import parametrize
|
309
|
-
from unittest_parametrize import ParametrizedTestCase
|
389
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
310
390
|
|
311
391
|
|
312
392
|
class RocketTests(ParametrizedTestCase):
|
@@ -318,8 +398,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
|
|
318
398
|
for hyperdrive_level in [0, 1, 2]
|
319
399
|
],
|
320
400
|
)
|
321
|
-
def test_takeoff(self, use_ions, hyperdrive_level) -> None:
|
322
|
-
...
|
401
|
+
def test_takeoff(self, use_ions, hyperdrive_level) -> None: ...
|
323
402
|
|
324
403
|
The above creates 2 * 3 = 6 versions of ``test_takeoff``.
|
325
404
|
|
@@ -331,8 +410,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
|
|
331
410
|
.. code-block:: python
|
332
411
|
|
333
412
|
from itertools import product
|
334
|
-
from unittest_parametrize import parametrize
|
335
|
-
from unittest_parametrize import ParametrizedTestCase
|
413
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
336
414
|
|
337
415
|
|
338
416
|
class RocketTests(ParametrizedTestCase):
|
@@ -346,8 +424,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
|
|
346
424
|
)
|
347
425
|
),
|
348
426
|
)
|
349
|
-
def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None:
|
350
|
-
...
|
427
|
+
def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None: ...
|
351
428
|
|
352
429
|
The above creates 2 * 3 * 2 = 12 versions of ``test_takeoff``.
|
353
430
|
|
@@ -371,15 +448,47 @@ To parametrize all tests within a test case, create a separate decorator and app
|
|
371
448
|
|
372
449
|
class StatsTests(ParametrizedTestCase):
|
373
450
|
@parametrize_race
|
374
|
-
def test_strength(self, race: str) -> None:
|
375
|
-
...
|
451
|
+
def test_strength(self, race: str) -> None: ...
|
376
452
|
|
377
453
|
@parametrize_race
|
378
|
-
def test_dexterity(self, race: str) -> None:
|
379
|
-
...
|
454
|
+
def test_dexterity(self, race: str) -> None: ...
|
380
455
|
|
381
456
|
...
|
382
457
|
|
458
|
+
Pass parameters in a dataclass
|
459
|
+
------------------------------
|
460
|
+
|
461
|
+
Thanks to `Florian Bruhin <https://bruhin.software/>`__ for this tip, from his `pytest tips and tricks presentation <https://bruhin.software/>`__.
|
462
|
+
|
463
|
+
If your test uses many parameters or cases, the parametrization may become unwieldy, as cases don’t name the arguments.
|
464
|
+
In this case, try using a `dataclass <https://docs.python.org/3/library/dataclasses.html>`__ to hold the arguments:
|
465
|
+
|
466
|
+
.. code-block:: python
|
467
|
+
|
468
|
+
from dataclasses import dataclass
|
469
|
+
|
470
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
471
|
+
|
472
|
+
|
473
|
+
@dataclass
|
474
|
+
class SquareParams:
|
475
|
+
x: int
|
476
|
+
expected: int
|
477
|
+
|
478
|
+
|
479
|
+
class SquareTests(ParametrizedTestCase):
|
480
|
+
@parametrize(
|
481
|
+
"sp",
|
482
|
+
[
|
483
|
+
(SquareParams(x=1, expected=1),),
|
484
|
+
(SquareParams(x=2, expected=4),),
|
485
|
+
],
|
486
|
+
)
|
487
|
+
def test_square(self, sp: SquareParams) -> None:
|
488
|
+
self.assertEqual(sp.x**2, sp.expected)
|
489
|
+
|
490
|
+
This way, each parameter is type-checked and named, improving safety and readability.
|
491
|
+
|
383
492
|
History
|
384
493
|
=======
|
385
494
|
|
@@ -33,7 +33,7 @@ Install with:
|
|
33
33
|
|
34
34
|
python -m pip install unittest-parametrize
|
35
35
|
|
36
|
-
Python 3.9 to 3.
|
36
|
+
Python 3.9 to 3.14 supported.
|
37
37
|
|
38
38
|
Usage
|
39
39
|
=====
|
@@ -51,15 +51,14 @@ There are two steps to parametrize a test case:
|
|
51
51
|
2. Apply ``@parametrize`` to any test methods for parametrization.
|
52
52
|
This decorator takes (at least):
|
53
53
|
|
54
|
-
* the argument names to parametrize, as comma-separated string
|
55
|
-
* a list of
|
54
|
+
* the argument names to parametrize, as comma-separated string or sequence of strings.
|
55
|
+
* a list of parameters to create individual tests for, which may be tuples, ``param`` objects, or single values (for one argument).
|
56
56
|
|
57
57
|
Here’s a basic example:
|
58
58
|
|
59
59
|
.. code-block:: python
|
60
60
|
|
61
|
-
from unittest_parametrize import parametrize
|
62
|
-
from unittest_parametrize import ParametrizedTestCase
|
61
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
63
62
|
|
64
63
|
|
65
64
|
class SquareTests(ParametrizedTestCase):
|
@@ -81,6 +80,25 @@ It supports both synchronous and asynchronous test methods.
|
|
81
80
|
.. |__init_subclass__ hook| replace:: ``__init_subclass__`` hook
|
82
81
|
__ https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__
|
83
82
|
|
83
|
+
Provide a single parameter without a wrapping tuple
|
84
|
+
---------------------------------------------------
|
85
|
+
|
86
|
+
If you only need a single parameter, you can provide values without wrapping them in tuples:
|
87
|
+
|
88
|
+
.. code-block:: python
|
89
|
+
|
90
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
91
|
+
|
92
|
+
|
93
|
+
class EqualTests(ParametrizedTestCase):
|
94
|
+
@parametrize(
|
95
|
+
"x",
|
96
|
+
[1, 2, 3],
|
97
|
+
)
|
98
|
+
def test_equal(self, x: int) -> None:
|
99
|
+
self.assertEqual(x, x)
|
100
|
+
|
101
|
+
|
84
102
|
Provide argument names as separate strings
|
85
103
|
------------------------------------------
|
86
104
|
|
@@ -88,8 +106,7 @@ You can provide argument names as a sequence of strings instead:
|
|
88
106
|
|
89
107
|
.. code-block:: python
|
90
108
|
|
91
|
-
from unittest_parametrize import parametrize
|
92
|
-
from unittest_parametrize import ParametrizedTestCase
|
109
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
93
110
|
|
94
111
|
|
95
112
|
class SquareTests(ParametrizedTestCase):
|
@@ -152,13 +169,20 @@ You can see these names when running the tests:
|
|
152
169
|
|
153
170
|
OK
|
154
171
|
|
155
|
-
You can customize these names
|
172
|
+
You can customize these names in several ways:
|
173
|
+
|
174
|
+
1. Using ``param`` objects with IDs.
|
175
|
+
2. Passing a sequence of strings as the ``ids`` argument.
|
176
|
+
3. Passing a callable as the ``ids`` argument.
|
177
|
+
|
178
|
+
Passing ``param`` objects with IDs
|
179
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
180
|
+
|
181
|
+
Pass a ``param`` object for each parameter set, setting the test ID suffix with the optional ``id`` argument:
|
156
182
|
|
157
183
|
.. code-block:: python
|
158
184
|
|
159
|
-
from unittest_parametrize import param
|
160
|
-
from unittest_parametrize import parametrize
|
161
|
-
from unittest_parametrize import ParametrizedTestCase
|
185
|
+
from unittest_parametrize import ParametrizedTestCase, param, parametrize
|
162
186
|
|
163
187
|
|
164
188
|
class SquareTests(ParametrizedTestCase):
|
@@ -172,7 +196,7 @@ You can customize these names by passing ``param`` objects, which contain the ar
|
|
172
196
|
def test_square(self, x: int, expected: int) -> None:
|
173
197
|
self.assertEqual(x**2, expected)
|
174
198
|
|
175
|
-
Yielding
|
199
|
+
Yielding more natural names:
|
176
200
|
|
177
201
|
.. code-block:: console
|
178
202
|
|
@@ -191,9 +215,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
|
|
191
215
|
|
192
216
|
.. code-block:: python
|
193
217
|
|
194
|
-
from unittest_parametrize import param
|
195
|
-
from unittest_parametrize import parametrize
|
196
|
-
from unittest_parametrize import ParametrizedTestCase
|
218
|
+
from unittest_parametrize import ParametrizedTestCase, param, parametrize
|
197
219
|
|
198
220
|
|
199
221
|
class SquareTests(ParametrizedTestCase):
|
@@ -207,7 +229,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
|
|
207
229
|
def test_square(self, x: int, expected: int) -> None:
|
208
230
|
self.assertEqual(x**2, expected)
|
209
231
|
|
210
|
-
ID-free ``param``\s fall back to the default index suffixes:
|
232
|
+
The ID-free ``param``\s fall back to the default index suffixes:
|
211
233
|
|
212
234
|
.. code-block:: console
|
213
235
|
|
@@ -220,12 +242,14 @@ ID-free ``param``\s fall back to the default index suffixes:
|
|
220
242
|
|
221
243
|
OK
|
222
244
|
|
223
|
-
|
245
|
+
Passing a sequence of strings as the ``ids`` argument
|
246
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
247
|
+
|
248
|
+
Another option is to provide the IDs in the separate ``ids`` argument:
|
224
249
|
|
225
250
|
.. code-block:: python
|
226
251
|
|
227
|
-
from unittest_parametrize import parametrize
|
228
|
-
from unittest_parametrize import ParametrizedTestCase
|
252
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
229
253
|
|
230
254
|
|
231
255
|
class SquareTests(ParametrizedTestCase):
|
@@ -240,6 +264,64 @@ Alternatively, you can provide the id’s separately with the ``ids`` argument:
|
|
240
264
|
def test_square(self, x: int, expected: int) -> None:
|
241
265
|
self.assertEqual(x**2, expected)
|
242
266
|
|
267
|
+
This option sets the full suffixes to the provided strings:
|
268
|
+
|
269
|
+
.. code-block:: console
|
270
|
+
|
271
|
+
$ python -m unittest t.py -v
|
272
|
+
test_square_one (example.SquareTests.test_square_one) ... ok
|
273
|
+
test_square_two (example.SquareTests.test_square_two) ... ok
|
274
|
+
|
275
|
+
----------------------------------------------------------------------
|
276
|
+
Ran 2 tests in 0.000s
|
277
|
+
|
278
|
+
OK
|
279
|
+
|
280
|
+
Passing a callable as the ``ids`` argument
|
281
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
282
|
+
|
283
|
+
The ``ids`` argument can also be a callable, which unittest-parametrize calls once per parameter value.
|
284
|
+
The callable can return a string for that value, or ``None`` to use the default index suffix.
|
285
|
+
The values are then joined with underscores to form the full suffix.
|
286
|
+
|
287
|
+
For example:
|
288
|
+
|
289
|
+
.. code-block:: python
|
290
|
+
|
291
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
292
|
+
|
293
|
+
|
294
|
+
def make_id(value):
|
295
|
+
if isinstance(value, int):
|
296
|
+
return f"num{value}"
|
297
|
+
return None
|
298
|
+
|
299
|
+
|
300
|
+
class SquareTests(ParametrizedTestCase):
|
301
|
+
@parametrize(
|
302
|
+
"x,expected",
|
303
|
+
[
|
304
|
+
(1, 1),
|
305
|
+
(2, 4),
|
306
|
+
],
|
307
|
+
ids=make_id,
|
308
|
+
)
|
309
|
+
def test_square(self, x: int, expected: int) -> None:
|
310
|
+
self.assertEqual(x**2, expected)
|
311
|
+
|
312
|
+
…yields:
|
313
|
+
|
314
|
+
.. code-block:: console
|
315
|
+
|
316
|
+
$ python -m unittest t.py -v
|
317
|
+
test_square_num1_num1 (example.SquareTests.test_square_num1_num1) ... ok
|
318
|
+
test_square_num2_num4 (example.SquareTests.test_square_num2_num4) ... ok
|
319
|
+
|
320
|
+
----------------------------------------------------------------------
|
321
|
+
Ran 2 tests in 0.000s
|
322
|
+
|
323
|
+
OK
|
324
|
+
|
243
325
|
Use with other test decorators
|
244
326
|
------------------------------
|
245
327
|
|
@@ -250,8 +332,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
|
|
250
332
|
.. code-block:: python
|
251
333
|
|
252
334
|
from unittest import mock
|
253
|
-
from unittest_parametrize import parametrize
|
254
|
-
from unittest_parametrize import ParametrizedTestCase
|
335
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
255
336
|
|
256
337
|
|
257
338
|
class CarpentryTests(ParametrizedTestCase):
|
@@ -260,8 +341,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
|
|
260
341
|
[(11,), (17,)],
|
261
342
|
)
|
262
343
|
@mock.patch("example.hammer", autospec=True)
|
263
|
-
def test_nail_a_board(self, mock_hammer, nails):
|
264
|
-
...
|
344
|
+
def test_nail_a_board(self, mock_hammer, nails): ...
|
265
345
|
|
266
346
|
Also note that due to how ``mock.patch`` always adds positional arguments at the start, the parametrized arguments must come last.
|
267
347
|
``@parametrize`` always adds parameters as keyword arguments, so you can also use `keyword-only syntax <https://peps.python.org/pep-3102/>`__ for parametrized arguments:
|
@@ -269,8 +349,7 @@ Also note that due to how ``mock.patch`` always adds positional arguments at the
|
|
269
349
|
.. code-block:: python
|
270
350
|
|
271
351
|
# ...
|
272
|
-
def test_nail_a_board(self, mock_hammer, *, nails):
|
273
|
-
...
|
352
|
+
def test_nail_a_board(self, mock_hammer, *, nails): ...
|
274
353
|
|
275
354
|
Multiple ``@parametrize`` decorators
|
276
355
|
------------------------------------
|
@@ -280,8 +359,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
|
|
280
359
|
|
281
360
|
.. code-block:: python
|
282
361
|
|
283
|
-
from unittest_parametrize import parametrize
|
284
|
-
from unittest_parametrize import ParametrizedTestCase
|
362
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
285
363
|
|
286
364
|
|
287
365
|
class RocketTests(ParametrizedTestCase):
|
@@ -293,8 +371,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
|
|
293
371
|
for hyperdrive_level in [0, 1, 2]
|
294
372
|
],
|
295
373
|
)
|
296
|
-
def test_takeoff(self, use_ions, hyperdrive_level) -> None:
|
297
|
-
...
|
374
|
+
def test_takeoff(self, use_ions, hyperdrive_level) -> None: ...
|
298
375
|
|
299
376
|
The above creates 2 * 3 = 6 versions of ``test_takeoff``.
|
300
377
|
|
@@ -306,8 +383,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
|
|
306
383
|
.. code-block:: python
|
307
384
|
|
308
385
|
from itertools import product
|
309
|
-
from unittest_parametrize import parametrize
|
310
|
-
from unittest_parametrize import ParametrizedTestCase
|
386
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
311
387
|
|
312
388
|
|
313
389
|
class RocketTests(ParametrizedTestCase):
|
@@ -321,8 +397,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
|
|
321
397
|
)
|
322
398
|
),
|
323
399
|
)
|
324
|
-
def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None:
|
325
|
-
...
|
400
|
+
def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None: ...
|
326
401
|
|
327
402
|
The above creates 2 * 3 * 2 = 12 versions of ``test_takeoff``.
|
328
403
|
|
@@ -346,15 +421,47 @@ To parametrize all tests within a test case, create a separate decorator and app
|
|
346
421
|
|
347
422
|
class StatsTests(ParametrizedTestCase):
|
348
423
|
@parametrize_race
|
349
|
-
def test_strength(self, race: str) -> None:
|
350
|
-
...
|
424
|
+
def test_strength(self, race: str) -> None: ...
|
351
425
|
|
352
426
|
@parametrize_race
|
353
|
-
def test_dexterity(self, race: str) -> None:
|
354
|
-
...
|
427
|
+
def test_dexterity(self, race: str) -> None: ...
|
355
428
|
|
356
429
|
...
|
357
430
|
|
431
|
+
Pass parameters in a dataclass
|
432
|
+
------------------------------
|
433
|
+
|
434
|
+
Thanks to `Florian Bruhin <https://bruhin.software/>`__ for this tip, from his `pytest tips and tricks presentation <https://bruhin.software/>`__.
|
435
|
+
|
436
|
+
If your test uses many parameters or cases, the parametrization may become unwieldy, as cases don’t name the arguments.
|
437
|
+
In this case, try using a `dataclass <https://docs.python.org/3/library/dataclasses.html>`__ to hold the arguments:
|
438
|
+
|
439
|
+
.. code-block:: python
|
440
|
+
|
441
|
+
from dataclasses import dataclass
|
442
|
+
|
443
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
444
|
+
|
445
|
+
|
446
|
+
@dataclass
|
447
|
+
class SquareParams:
|
448
|
+
x: int
|
449
|
+
expected: int
|
450
|
+
|
451
|
+
|
452
|
+
class SquareTests(ParametrizedTestCase):
|
453
|
+
@parametrize(
|
454
|
+
"sp",
|
455
|
+
[
|
456
|
+
(SquareParams(x=1, expected=1),),
|
457
|
+
(SquareParams(x=2, expected=4),),
|
458
|
+
],
|
459
|
+
)
|
460
|
+
def test_square(self, sp: SquareParams) -> None:
|
461
|
+
self.assertEqual(sp.x**2, sp.expected)
|
462
|
+
|
463
|
+
This way, each parameter is type-checked and named, improving safety and readability.
|
464
|
+
|
358
465
|
History
|
359
466
|
=======
|
360
467
|
|
@@ -1,17 +1,19 @@
|
|
1
1
|
[build-system]
|
2
2
|
build-backend = "setuptools.build_meta"
|
3
3
|
requires = [
|
4
|
-
"setuptools",
|
4
|
+
"setuptools>=77",
|
5
5
|
]
|
6
6
|
|
7
7
|
[project]
|
8
8
|
name = "unittest-parametrize"
|
9
|
-
version = "1.
|
9
|
+
version = "1.8.0"
|
10
10
|
description = "Parametrize tests within unittest TestCases."
|
11
11
|
readme = "README.rst"
|
12
12
|
keywords = [
|
13
13
|
"unittest",
|
14
14
|
]
|
15
|
+
license = "MIT"
|
16
|
+
license-files = [ "LICENSE" ]
|
15
17
|
authors = [
|
16
18
|
{ name = "Adam Johnson", email = "me@adamj.eu" },
|
17
19
|
]
|
@@ -19,7 +21,6 @@ requires-python = ">=3.9"
|
|
19
21
|
classifiers = [
|
20
22
|
"Development Status :: 5 - Production/Stable",
|
21
23
|
"Intended Audience :: Developers",
|
22
|
-
"License :: OSI Approved :: MIT License",
|
23
24
|
"Natural Language :: English",
|
24
25
|
"Programming Language :: Python :: 3 :: Only",
|
25
26
|
"Programming Language :: Python :: 3.9",
|
@@ -27,6 +28,7 @@ classifiers = [
|
|
27
28
|
"Programming Language :: Python :: 3.11",
|
28
29
|
"Programming Language :: Python :: 3.12",
|
29
30
|
"Programming Language :: Python :: 3.13",
|
31
|
+
"Programming Language :: Python :: 3.14",
|
30
32
|
"Typing :: Typed",
|
31
33
|
]
|
32
34
|
dependencies = [
|
@@ -36,15 +38,53 @@ urls.Changelog = "https://github.com/adamchainz/unittest-parametrize/blob/main/C
|
|
36
38
|
urls.Funding = "https://adamj.eu/books/"
|
37
39
|
urls.Repository = "https://github.com/adamchainz/unittest-parametrize"
|
38
40
|
|
39
|
-
[
|
40
|
-
|
41
|
-
"
|
41
|
+
[dependency-groups]
|
42
|
+
test = [
|
43
|
+
"coverage[toml]",
|
44
|
+
"pytest",
|
45
|
+
"pytest-randomly",
|
46
|
+
"typing-extensions; python_version<'3.10'",
|
47
|
+
]
|
48
|
+
|
49
|
+
[tool.ruff]
|
50
|
+
lint.select = [
|
51
|
+
# flake8-bugbear
|
52
|
+
"B",
|
53
|
+
# flake8-comprehensions
|
54
|
+
"C4",
|
55
|
+
# pycodestyle
|
56
|
+
"E",
|
57
|
+
# Pyflakes errors
|
58
|
+
"F",
|
59
|
+
# isort
|
60
|
+
"I",
|
61
|
+
# flake8-simplify
|
62
|
+
"SIM",
|
63
|
+
# flake8-tidy-imports
|
64
|
+
"TID",
|
65
|
+
# pyupgrade
|
66
|
+
"UP",
|
67
|
+
# Pyflakes warnings
|
68
|
+
"W",
|
69
|
+
]
|
70
|
+
lint.ignore = [
|
71
|
+
# flake8-bugbear opinionated rules
|
72
|
+
"B9",
|
73
|
+
# line-too-long
|
74
|
+
"E501",
|
75
|
+
# suppressible-exception
|
76
|
+
"SIM105",
|
77
|
+
# if-else-block-instead-of-if-exp
|
78
|
+
"SIM108",
|
79
|
+
]
|
80
|
+
lint.extend-safe-fixes = [
|
81
|
+
# non-pep585-annotation
|
82
|
+
"UP006",
|
42
83
|
]
|
43
|
-
|
44
|
-
profile = "black"
|
84
|
+
lint.isort.required-imports = [ "from __future__ import annotations" ]
|
45
85
|
|
46
86
|
[tool.pyproject-fmt]
|
47
|
-
max_supported_python = "3.
|
87
|
+
max_supported_python = "3.14"
|
48
88
|
|
49
89
|
[tool.pytest.ini_options]
|
50
90
|
addopts = """\
|
{unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0}/src/unittest_parametrize/__init__.py
RENAMED
@@ -5,9 +5,7 @@ import sys
|
|
5
5
|
from collections.abc import Sequence
|
6
6
|
from functools import wraps
|
7
7
|
from types import FunctionType
|
8
|
-
from typing import Any
|
9
|
-
from typing import Callable
|
10
|
-
from typing import TypeVar
|
8
|
+
from typing import Any, Callable, TypeVar
|
11
9
|
from unittest import TestCase
|
12
10
|
|
13
11
|
if sys.version_info >= (3, 10):
|
@@ -122,8 +120,8 @@ TestFunc = Callable[P, T]
|
|
122
120
|
|
123
121
|
def parametrize(
|
124
122
|
argnames: str | Sequence[str],
|
125
|
-
argvalues: Sequence[tuple[Any, ...]
|
126
|
-
ids: Sequence[str | None] | None = None,
|
123
|
+
argvalues: Sequence[tuple[Any, ...] | param | Any],
|
124
|
+
ids: Sequence[str | None] | Callable[[Any], str | None] | None = None,
|
127
125
|
) -> Callable[[Callable[P, T]], Callable[P, T]]:
|
128
126
|
if isinstance(argnames, str):
|
129
127
|
argnames = [a.strip() for a in argnames.split(",")]
|
@@ -131,24 +129,22 @@ def parametrize(
|
|
131
129
|
if len(argnames) == 0:
|
132
130
|
raise ValueError("argnames must contain at least one element")
|
133
131
|
|
134
|
-
|
132
|
+
ids_callable = callable(ids)
|
133
|
+
if ids is not None and not ids_callable and len(ids) != len(argvalues): # type: ignore[arg-type]
|
135
134
|
raise ValueError("ids must have the same length as argvalues")
|
136
135
|
|
137
136
|
seen_ids = set()
|
138
137
|
params = []
|
139
138
|
for i, argvalue in enumerate(argvalues):
|
140
|
-
if ids and ids[i]:
|
141
|
-
id_ = ids[i]
|
142
|
-
else:
|
143
|
-
id_ = str(i)
|
144
|
-
|
145
139
|
if isinstance(argvalue, tuple):
|
146
140
|
if len(argvalue) != len(argnames):
|
147
141
|
raise ValueError(
|
148
142
|
f"tuple at index {i} has wrong number of arguments "
|
149
143
|
+ f"({len(argvalue)} != {len(argnames)})"
|
150
144
|
)
|
151
|
-
|
145
|
+
argvalue = param(*argvalue, id=make_id(i, argvalue, ids))
|
146
|
+
params.append(argvalue)
|
147
|
+
seen_ids.add(argvalue.id)
|
152
148
|
elif isinstance(argvalue, param):
|
153
149
|
if len(argvalue.args) != len(argnames):
|
154
150
|
raise ValueError(
|
@@ -157,15 +153,18 @@ def parametrize(
|
|
157
153
|
)
|
158
154
|
|
159
155
|
if argvalue.id is None:
|
160
|
-
argvalue = param(*argvalue.args, id=
|
156
|
+
argvalue = param(*argvalue.args, id=make_id(i, argvalue, ids))
|
161
157
|
if argvalue.id in seen_ids:
|
162
158
|
raise ValueError(f"Duplicate param id {argvalue.id!r}")
|
163
159
|
seen_ids.add(argvalue.id)
|
164
160
|
params.append(argvalue)
|
165
|
-
|
161
|
+
elif len(argnames) == 1:
|
162
|
+
argvalue = param(argvalue, id=make_id(i, (argvalue,), ids))
|
163
|
+
seen_ids.add(argvalue.id)
|
164
|
+
params.append(argvalue)
|
166
165
|
else:
|
167
166
|
raise TypeError(
|
168
|
-
f"argvalue at index {i} is not a tuple or
|
167
|
+
f"argvalue at index {i} is not a tuple, param instance, or single value: {argvalue!r}"
|
169
168
|
)
|
170
169
|
|
171
170
|
_parametrized = parametrized(argnames, params)
|
@@ -183,3 +182,34 @@ def parametrize(
|
|
183
182
|
return func
|
184
183
|
|
185
184
|
return wrapper
|
185
|
+
|
186
|
+
|
187
|
+
def make_id(
|
188
|
+
i: int,
|
189
|
+
argvalue: tuple[Any, ...] | param,
|
190
|
+
ids: Sequence[str | None] | Callable[[Any], str | None] | None,
|
191
|
+
) -> str:
|
192
|
+
if callable(ids):
|
193
|
+
if isinstance(argvalue, tuple):
|
194
|
+
values = argvalue
|
195
|
+
else:
|
196
|
+
values = argvalue.args
|
197
|
+
|
198
|
+
id_parts = []
|
199
|
+
for value in values:
|
200
|
+
id_part = ids(value)
|
201
|
+
if id_part is not None:
|
202
|
+
id_parts.append(id_part)
|
203
|
+
else:
|
204
|
+
id_parts.append(str(value))
|
205
|
+
id_ = "_".join(id_parts)
|
206
|
+
# Validate the generated ID
|
207
|
+
if not f"_{id_}".isidentifier():
|
208
|
+
raise ValueError(
|
209
|
+
f"callable ids returned invalid Python identifier suffix: {id_!r}"
|
210
|
+
)
|
211
|
+
return id_
|
212
|
+
elif ids and ids[i]:
|
213
|
+
return str(ids[i])
|
214
|
+
else:
|
215
|
+
return str(i)
|
{unittest_parametrize-1.6.0 → unittest_parametrize-1.8.0/src/unittest_parametrize.egg-info}/PKG-INFO
RENAMED
@@ -1,15 +1,15 @@
|
|
1
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.4
|
2
2
|
Name: unittest-parametrize
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.8.0
|
4
4
|
Summary: Parametrize tests within unittest TestCases.
|
5
5
|
Author-email: Adam Johnson <me@adamj.eu>
|
6
|
+
License-Expression: MIT
|
6
7
|
Project-URL: Changelog, https://github.com/adamchainz/unittest-parametrize/blob/main/CHANGELOG.rst
|
7
8
|
Project-URL: Funding, https://adamj.eu/books/
|
8
9
|
Project-URL: Repository, https://github.com/adamchainz/unittest-parametrize
|
9
10
|
Keywords: unittest
|
10
11
|
Classifier: Development Status :: 5 - Production/Stable
|
11
12
|
Classifier: Intended Audience :: Developers
|
12
|
-
Classifier: License :: OSI Approved :: MIT License
|
13
13
|
Classifier: Natural Language :: English
|
14
14
|
Classifier: Programming Language :: Python :: 3 :: Only
|
15
15
|
Classifier: Programming Language :: Python :: 3.9
|
@@ -17,11 +17,13 @@ Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
19
19
|
Classifier: Programming Language :: Python :: 3.13
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
20
21
|
Classifier: Typing :: Typed
|
21
22
|
Requires-Python: >=3.9
|
22
23
|
Description-Content-Type: text/x-rst
|
23
24
|
License-File: LICENSE
|
24
25
|
Requires-Dist: typing-extensions; python_version < "3.10"
|
26
|
+
Dynamic: license-file
|
25
27
|
|
26
28
|
====================
|
27
29
|
unittest-parametrize
|
@@ -58,7 +60,7 @@ Install with:
|
|
58
60
|
|
59
61
|
python -m pip install unittest-parametrize
|
60
62
|
|
61
|
-
Python 3.9 to 3.
|
63
|
+
Python 3.9 to 3.14 supported.
|
62
64
|
|
63
65
|
Usage
|
64
66
|
=====
|
@@ -76,15 +78,14 @@ There are two steps to parametrize a test case:
|
|
76
78
|
2. Apply ``@parametrize`` to any test methods for parametrization.
|
77
79
|
This decorator takes (at least):
|
78
80
|
|
79
|
-
* the argument names to parametrize, as comma-separated string
|
80
|
-
* a list of
|
81
|
+
* the argument names to parametrize, as comma-separated string or sequence of strings.
|
82
|
+
* a list of parameters to create individual tests for, which may be tuples, ``param`` objects, or single values (for one argument).
|
81
83
|
|
82
84
|
Here’s a basic example:
|
83
85
|
|
84
86
|
.. code-block:: python
|
85
87
|
|
86
|
-
from unittest_parametrize import parametrize
|
87
|
-
from unittest_parametrize import ParametrizedTestCase
|
88
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
88
89
|
|
89
90
|
|
90
91
|
class SquareTests(ParametrizedTestCase):
|
@@ -106,6 +107,25 @@ It supports both synchronous and asynchronous test methods.
|
|
106
107
|
.. |__init_subclass__ hook| replace:: ``__init_subclass__`` hook
|
107
108
|
__ https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__
|
108
109
|
|
110
|
+
Provide a single parameter without a wrapping tuple
|
111
|
+
---------------------------------------------------
|
112
|
+
|
113
|
+
If you only need a single parameter, you can provide values without wrapping them in tuples:
|
114
|
+
|
115
|
+
.. code-block:: python
|
116
|
+
|
117
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
118
|
+
|
119
|
+
|
120
|
+
class EqualTests(ParametrizedTestCase):
|
121
|
+
@parametrize(
|
122
|
+
"x",
|
123
|
+
[1, 2, 3],
|
124
|
+
)
|
125
|
+
def test_equal(self, x: int) -> None:
|
126
|
+
self.assertEqual(x, x)
|
127
|
+
|
128
|
+
|
109
129
|
Provide argument names as separate strings
|
110
130
|
------------------------------------------
|
111
131
|
|
@@ -113,8 +133,7 @@ You can provide argument names as a sequence of strings instead:
|
|
113
133
|
|
114
134
|
.. code-block:: python
|
115
135
|
|
116
|
-
from unittest_parametrize import parametrize
|
117
|
-
from unittest_parametrize import ParametrizedTestCase
|
136
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
118
137
|
|
119
138
|
|
120
139
|
class SquareTests(ParametrizedTestCase):
|
@@ -177,13 +196,20 @@ You can see these names when running the tests:
|
|
177
196
|
|
178
197
|
OK
|
179
198
|
|
180
|
-
You can customize these names
|
199
|
+
You can customize these names in several ways:
|
200
|
+
|
201
|
+
1. Using ``param`` objects with IDs.
|
202
|
+
2. Passing a sequence of strings as the ``ids`` argument.
|
203
|
+
3. Passing a callable as the ``ids`` argument.
|
204
|
+
|
205
|
+
Passing ``param`` objects with IDs
|
206
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
207
|
+
|
208
|
+
Pass a ``param`` object for each parameter set, setting the test ID suffix with the optional ``id`` argument:
|
181
209
|
|
182
210
|
.. code-block:: python
|
183
211
|
|
184
|
-
from unittest_parametrize import param
|
185
|
-
from unittest_parametrize import parametrize
|
186
|
-
from unittest_parametrize import ParametrizedTestCase
|
212
|
+
from unittest_parametrize import ParametrizedTestCase, param, parametrize
|
187
213
|
|
188
214
|
|
189
215
|
class SquareTests(ParametrizedTestCase):
|
@@ -197,7 +223,7 @@ You can customize these names by passing ``param`` objects, which contain the ar
|
|
197
223
|
def test_square(self, x: int, expected: int) -> None:
|
198
224
|
self.assertEqual(x**2, expected)
|
199
225
|
|
200
|
-
Yielding
|
226
|
+
Yielding more natural names:
|
201
227
|
|
202
228
|
.. code-block:: console
|
203
229
|
|
@@ -216,9 +242,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
|
|
216
242
|
|
217
243
|
.. code-block:: python
|
218
244
|
|
219
|
-
from unittest_parametrize import param
|
220
|
-
from unittest_parametrize import parametrize
|
221
|
-
from unittest_parametrize import ParametrizedTestCase
|
245
|
+
from unittest_parametrize import ParametrizedTestCase, param, parametrize
|
222
246
|
|
223
247
|
|
224
248
|
class SquareTests(ParametrizedTestCase):
|
@@ -232,7 +256,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
|
|
232
256
|
def test_square(self, x: int, expected: int) -> None:
|
233
257
|
self.assertEqual(x**2, expected)
|
234
258
|
|
235
|
-
ID-free ``param``\s fall back to the default index suffixes:
|
259
|
+
The ID-free ``param``\s fall back to the default index suffixes:
|
236
260
|
|
237
261
|
.. code-block:: console
|
238
262
|
|
@@ -245,12 +269,14 @@ ID-free ``param``\s fall back to the default index suffixes:
|
|
245
269
|
|
246
270
|
OK
|
247
271
|
|
248
|
-
|
272
|
+
Passing a sequence of strings as the ``ids`` argument
|
273
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
274
|
+
|
275
|
+
Another option is to provide the IDs in the separate ``ids`` argument:
|
249
276
|
|
250
277
|
.. code-block:: python
|
251
278
|
|
252
|
-
from unittest_parametrize import parametrize
|
253
|
-
from unittest_parametrize import ParametrizedTestCase
|
279
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
254
280
|
|
255
281
|
|
256
282
|
class SquareTests(ParametrizedTestCase):
|
@@ -265,6 +291,64 @@ Alternatively, you can provide the id’s separately with the ``ids`` argument:
|
|
265
291
|
def test_square(self, x: int, expected: int) -> None:
|
266
292
|
self.assertEqual(x**2, expected)
|
267
293
|
|
294
|
+
This option sets the full suffixes to the provided strings:
|
295
|
+
|
296
|
+
.. code-block:: console
|
297
|
+
|
298
|
+
$ python -m unittest t.py -v
|
299
|
+
test_square_one (example.SquareTests.test_square_one) ... ok
|
300
|
+
test_square_two (example.SquareTests.test_square_two) ... ok
|
301
|
+
|
302
|
+
----------------------------------------------------------------------
|
303
|
+
Ran 2 tests in 0.000s
|
304
|
+
|
305
|
+
OK
|
306
|
+
|
307
|
+
Passing a callable as the ``ids`` argument
|
308
|
+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
309
|
+
|
310
|
+
The ``ids`` argument can also be a callable, which unittest-parametrize calls once per parameter value.
|
311
|
+
The callable can return a string for that value, or ``None`` to use the default index suffix.
|
312
|
+
The values are then joined with underscores to form the full suffix.
|
313
|
+
|
314
|
+
For example:
|
315
|
+
|
316
|
+
.. code-block:: python
|
317
|
+
|
318
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
319
|
+
|
320
|
+
|
321
|
+
def make_id(value):
|
322
|
+
if isinstance(value, int):
|
323
|
+
return f"num{value}"
|
324
|
+
return None
|
325
|
+
|
326
|
+
|
327
|
+
class SquareTests(ParametrizedTestCase):
|
328
|
+
@parametrize(
|
329
|
+
"x,expected",
|
330
|
+
[
|
331
|
+
(1, 1),
|
332
|
+
(2, 4),
|
333
|
+
],
|
334
|
+
ids=make_id,
|
335
|
+
)
|
336
|
+
def test_square(self, x: int, expected: int) -> None:
|
337
|
+
self.assertEqual(x**2, expected)
|
338
|
+
|
339
|
+
…yields:
|
340
|
+
|
341
|
+
.. code-block:: console
|
342
|
+
|
343
|
+
$ python -m unittest t.py -v
|
344
|
+
test_square_num1_num1 (example.SquareTests.test_square_num1_num1) ... ok
|
345
|
+
test_square_num2_num4 (example.SquareTests.test_square_num2_num4) ... ok
|
346
|
+
|
347
|
+
----------------------------------------------------------------------
|
348
|
+
Ran 2 tests in 0.000s
|
349
|
+
|
350
|
+
OK
|
351
|
+
|
268
352
|
Use with other test decorators
|
269
353
|
------------------------------
|
270
354
|
|
@@ -275,8 +359,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
|
|
275
359
|
.. code-block:: python
|
276
360
|
|
277
361
|
from unittest import mock
|
278
|
-
from unittest_parametrize import parametrize
|
279
|
-
from unittest_parametrize import ParametrizedTestCase
|
362
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
280
363
|
|
281
364
|
|
282
365
|
class CarpentryTests(ParametrizedTestCase):
|
@@ -285,8 +368,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
|
|
285
368
|
[(11,), (17,)],
|
286
369
|
)
|
287
370
|
@mock.patch("example.hammer", autospec=True)
|
288
|
-
def test_nail_a_board(self, mock_hammer, nails):
|
289
|
-
...
|
371
|
+
def test_nail_a_board(self, mock_hammer, nails): ...
|
290
372
|
|
291
373
|
Also note that due to how ``mock.patch`` always adds positional arguments at the start, the parametrized arguments must come last.
|
292
374
|
``@parametrize`` always adds parameters as keyword arguments, so you can also use `keyword-only syntax <https://peps.python.org/pep-3102/>`__ for parametrized arguments:
|
@@ -294,8 +376,7 @@ Also note that due to how ``mock.patch`` always adds positional arguments at the
|
|
294
376
|
.. code-block:: python
|
295
377
|
|
296
378
|
# ...
|
297
|
-
def test_nail_a_board(self, mock_hammer, *, nails):
|
298
|
-
...
|
379
|
+
def test_nail_a_board(self, mock_hammer, *, nails): ...
|
299
380
|
|
300
381
|
Multiple ``@parametrize`` decorators
|
301
382
|
------------------------------------
|
@@ -305,8 +386,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
|
|
305
386
|
|
306
387
|
.. code-block:: python
|
307
388
|
|
308
|
-
from unittest_parametrize import parametrize
|
309
|
-
from unittest_parametrize import ParametrizedTestCase
|
389
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
310
390
|
|
311
391
|
|
312
392
|
class RocketTests(ParametrizedTestCase):
|
@@ -318,8 +398,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
|
|
318
398
|
for hyperdrive_level in [0, 1, 2]
|
319
399
|
],
|
320
400
|
)
|
321
|
-
def test_takeoff(self, use_ions, hyperdrive_level) -> None:
|
322
|
-
...
|
401
|
+
def test_takeoff(self, use_ions, hyperdrive_level) -> None: ...
|
323
402
|
|
324
403
|
The above creates 2 * 3 = 6 versions of ``test_takeoff``.
|
325
404
|
|
@@ -331,8 +410,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
|
|
331
410
|
.. code-block:: python
|
332
411
|
|
333
412
|
from itertools import product
|
334
|
-
from unittest_parametrize import parametrize
|
335
|
-
from unittest_parametrize import ParametrizedTestCase
|
413
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
336
414
|
|
337
415
|
|
338
416
|
class RocketTests(ParametrizedTestCase):
|
@@ -346,8 +424,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
|
|
346
424
|
)
|
347
425
|
),
|
348
426
|
)
|
349
|
-
def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None:
|
350
|
-
...
|
427
|
+
def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None: ...
|
351
428
|
|
352
429
|
The above creates 2 * 3 * 2 = 12 versions of ``test_takeoff``.
|
353
430
|
|
@@ -371,15 +448,47 @@ To parametrize all tests within a test case, create a separate decorator and app
|
|
371
448
|
|
372
449
|
class StatsTests(ParametrizedTestCase):
|
373
450
|
@parametrize_race
|
374
|
-
def test_strength(self, race: str) -> None:
|
375
|
-
...
|
451
|
+
def test_strength(self, race: str) -> None: ...
|
376
452
|
|
377
453
|
@parametrize_race
|
378
|
-
def test_dexterity(self, race: str) -> None:
|
379
|
-
...
|
454
|
+
def test_dexterity(self, race: str) -> None: ...
|
380
455
|
|
381
456
|
...
|
382
457
|
|
458
|
+
Pass parameters in a dataclass
|
459
|
+
------------------------------
|
460
|
+
|
461
|
+
Thanks to `Florian Bruhin <https://bruhin.software/>`__ for this tip, from his `pytest tips and tricks presentation <https://bruhin.software/>`__.
|
462
|
+
|
463
|
+
If your test uses many parameters or cases, the parametrization may become unwieldy, as cases don’t name the arguments.
|
464
|
+
In this case, try using a `dataclass <https://docs.python.org/3/library/dataclasses.html>`__ to hold the arguments:
|
465
|
+
|
466
|
+
.. code-block:: python
|
467
|
+
|
468
|
+
from dataclasses import dataclass
|
469
|
+
|
470
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
471
|
+
|
472
|
+
|
473
|
+
@dataclass
|
474
|
+
class SquareParams:
|
475
|
+
x: int
|
476
|
+
expected: int
|
477
|
+
|
478
|
+
|
479
|
+
class SquareTests(ParametrizedTestCase):
|
480
|
+
@parametrize(
|
481
|
+
"sp",
|
482
|
+
[
|
483
|
+
(SquareParams(x=1, expected=1),),
|
484
|
+
(SquareParams(x=2, expected=4),),
|
485
|
+
],
|
486
|
+
)
|
487
|
+
def test_square(self, sp: SquareParams) -> None:
|
488
|
+
self.assertEqual(sp.x**2, sp.expected)
|
489
|
+
|
490
|
+
This way, each parameter is type-checked and named, improving safety and readability.
|
491
|
+
|
383
492
|
History
|
384
493
|
=======
|
385
494
|
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|