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