unittest-parametrize 1.6.0__py3-none-any.whl → 1.7.0__py3-none-any.whl
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/__init__.py +40 -13
- {unittest_parametrize-1.6.0.dist-info → unittest_parametrize-1.7.0.dist-info}/METADATA +126 -37
- unittest_parametrize-1.7.0.dist-info/RECORD +7 -0
- {unittest_parametrize-1.6.0.dist-info → unittest_parametrize-1.7.0.dist-info}/WHEEL +1 -1
- unittest_parametrize-1.6.0.dist-info/RECORD +0 -7
- {unittest_parametrize-1.6.0.dist-info → unittest_parametrize-1.7.0.dist-info/licenses}/LICENSE +0 -0
- {unittest_parametrize-1.6.0.dist-info → unittest_parametrize-1.7.0.dist-info}/top_level.txt +0 -0
unittest_parametrize/__init__.py
CHANGED
@@ -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],
|
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,7 +153,7 @@ 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)
|
@@ -183,3 +179,34 @@ def parametrize(
|
|
183
179
|
return func
|
184
180
|
|
185
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)
|
@@ -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
|
@@ -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):
|
@@ -113,8 +113,7 @@ You can provide argument names as a sequence of strings instead:
|
|
113
113
|
|
114
114
|
.. code-block:: python
|
115
115
|
|
116
|
-
from unittest_parametrize import parametrize
|
117
|
-
from unittest_parametrize import ParametrizedTestCase
|
116
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
118
117
|
|
119
118
|
|
120
119
|
class SquareTests(ParametrizedTestCase):
|
@@ -177,13 +176,20 @@ You can see these names when running the tests:
|
|
177
176
|
|
178
177
|
OK
|
179
178
|
|
180
|
-
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:
|
181
189
|
|
182
190
|
.. code-block:: python
|
183
191
|
|
184
|
-
from unittest_parametrize import param
|
185
|
-
from unittest_parametrize import parametrize
|
186
|
-
from unittest_parametrize import ParametrizedTestCase
|
192
|
+
from unittest_parametrize import ParametrizedTestCase, param, parametrize
|
187
193
|
|
188
194
|
|
189
195
|
class SquareTests(ParametrizedTestCase):
|
@@ -197,7 +203,7 @@ You can customize these names by passing ``param`` objects, which contain the ar
|
|
197
203
|
def test_square(self, x: int, expected: int) -> None:
|
198
204
|
self.assertEqual(x**2, expected)
|
199
205
|
|
200
|
-
Yielding
|
206
|
+
Yielding more natural names:
|
201
207
|
|
202
208
|
.. code-block:: console
|
203
209
|
|
@@ -216,9 +222,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
|
|
216
222
|
|
217
223
|
.. code-block:: python
|
218
224
|
|
219
|
-
from unittest_parametrize import param
|
220
|
-
from unittest_parametrize import parametrize
|
221
|
-
from unittest_parametrize import ParametrizedTestCase
|
225
|
+
from unittest_parametrize import ParametrizedTestCase, param, parametrize
|
222
226
|
|
223
227
|
|
224
228
|
class SquareTests(ParametrizedTestCase):
|
@@ -232,7 +236,7 @@ Since parameter IDs are optional, you can provide them only for some tests:
|
|
232
236
|
def test_square(self, x: int, expected: int) -> None:
|
233
237
|
self.assertEqual(x**2, expected)
|
234
238
|
|
235
|
-
ID-free ``param``\s fall back to the default index suffixes:
|
239
|
+
The ID-free ``param``\s fall back to the default index suffixes:
|
236
240
|
|
237
241
|
.. code-block:: console
|
238
242
|
|
@@ -245,12 +249,14 @@ ID-free ``param``\s fall back to the default index suffixes:
|
|
245
249
|
|
246
250
|
OK
|
247
251
|
|
248
|
-
|
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:
|
249
256
|
|
250
257
|
.. code-block:: python
|
251
258
|
|
252
|
-
from unittest_parametrize import parametrize
|
253
|
-
from unittest_parametrize import ParametrizedTestCase
|
259
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
254
260
|
|
255
261
|
|
256
262
|
class SquareTests(ParametrizedTestCase):
|
@@ -265,6 +271,64 @@ Alternatively, you can provide the id’s separately with the ``ids`` argument:
|
|
265
271
|
def test_square(self, x: int, expected: int) -> None:
|
266
272
|
self.assertEqual(x**2, expected)
|
267
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
|
+
|
268
332
|
Use with other test decorators
|
269
333
|
------------------------------
|
270
334
|
|
@@ -275,8 +339,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
|
|
275
339
|
.. code-block:: python
|
276
340
|
|
277
341
|
from unittest import mock
|
278
|
-
from unittest_parametrize import parametrize
|
279
|
-
from unittest_parametrize import ParametrizedTestCase
|
342
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
280
343
|
|
281
344
|
|
282
345
|
class CarpentryTests(ParametrizedTestCase):
|
@@ -285,8 +348,7 @@ So decorators like ``@mock.patch`` need be beneath ``@parametrize``:
|
|
285
348
|
[(11,), (17,)],
|
286
349
|
)
|
287
350
|
@mock.patch("example.hammer", autospec=True)
|
288
|
-
def test_nail_a_board(self, mock_hammer, nails):
|
289
|
-
...
|
351
|
+
def test_nail_a_board(self, mock_hammer, nails): ...
|
290
352
|
|
291
353
|
Also note that due to how ``mock.patch`` always adds positional arguments at the start, the parametrized arguments must come last.
|
292
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:
|
@@ -294,8 +356,7 @@ Also note that due to how ``mock.patch`` always adds positional arguments at the
|
|
294
356
|
.. code-block:: python
|
295
357
|
|
296
358
|
# ...
|
297
|
-
def test_nail_a_board(self, mock_hammer, *, nails):
|
298
|
-
...
|
359
|
+
def test_nail_a_board(self, mock_hammer, *, nails): ...
|
299
360
|
|
300
361
|
Multiple ``@parametrize`` decorators
|
301
362
|
------------------------------------
|
@@ -305,8 +366,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
|
|
305
366
|
|
306
367
|
.. code-block:: python
|
307
368
|
|
308
|
-
from unittest_parametrize import parametrize
|
309
|
-
from unittest_parametrize import ParametrizedTestCase
|
369
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
310
370
|
|
311
371
|
|
312
372
|
class RocketTests(ParametrizedTestCase):
|
@@ -318,8 +378,7 @@ To create a cross-product of tests, you can use nested list comprehensions:
|
|
318
378
|
for hyperdrive_level in [0, 1, 2]
|
319
379
|
],
|
320
380
|
)
|
321
|
-
def test_takeoff(self, use_ions, hyperdrive_level) -> None:
|
322
|
-
...
|
381
|
+
def test_takeoff(self, use_ions, hyperdrive_level) -> None: ...
|
323
382
|
|
324
383
|
The above creates 2 * 3 = 6 versions of ``test_takeoff``.
|
325
384
|
|
@@ -331,8 +390,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
|
|
331
390
|
.. code-block:: python
|
332
391
|
|
333
392
|
from itertools import product
|
334
|
-
from unittest_parametrize import parametrize
|
335
|
-
from unittest_parametrize import ParametrizedTestCase
|
393
|
+
from unittest_parametrize import ParametrizedTestCase, parametrize
|
336
394
|
|
337
395
|
|
338
396
|
class RocketTests(ParametrizedTestCase):
|
@@ -346,8 +404,7 @@ __ https://docs.python.org/3/library/itertools.html#itertools.product
|
|
346
404
|
)
|
347
405
|
),
|
348
406
|
)
|
349
|
-
def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None:
|
350
|
-
...
|
407
|
+
def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None: ...
|
351
408
|
|
352
409
|
The above creates 2 * 3 * 2 = 12 versions of ``test_takeoff``.
|
353
410
|
|
@@ -371,15 +428,47 @@ To parametrize all tests within a test case, create a separate decorator and app
|
|
371
428
|
|
372
429
|
class StatsTests(ParametrizedTestCase):
|
373
430
|
@parametrize_race
|
374
|
-
def test_strength(self, race: str) -> None:
|
375
|
-
...
|
431
|
+
def test_strength(self, race: str) -> None: ...
|
376
432
|
|
377
433
|
@parametrize_race
|
378
|
-
def test_dexterity(self, race: str) -> None:
|
379
|
-
...
|
434
|
+
def test_dexterity(self, race: str) -> None: ...
|
380
435
|
|
381
436
|
...
|
382
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
|
+
|
383
472
|
History
|
384
473
|
=======
|
385
474
|
|
@@ -0,0 +1,7 @@
|
|
1
|
+
unittest_parametrize/__init__.py,sha256=32m2wHL1et5GcNetK-RA3FVksCf_5ChauRxVK3l2vJc,7234
|
2
|
+
unittest_parametrize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
+
unittest_parametrize-1.7.0.dist-info/licenses/LICENSE,sha256=Jn179RflkRZXhSCGt_D7asLy2Ex47C0WdBHxe0lBazk,1069
|
4
|
+
unittest_parametrize-1.7.0.dist-info/METADATA,sha256=iD6hm8ej4zGlHDAvI_4WwPKVyIzS2-U_zrqPYDYOVSY,17807
|
5
|
+
unittest_parametrize-1.7.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
6
|
+
unittest_parametrize-1.7.0.dist-info/top_level.txt,sha256=4nLqNaGtBX8Ny36ZdFt37Gk-87hPtOvfdfZlTBi3OtU,21
|
7
|
+
unittest_parametrize-1.7.0.dist-info/RECORD,,
|
@@ -1,7 +0,0 @@
|
|
1
|
-
unittest_parametrize/__init__.py,sha256=-WNbay-GoS2_ENp9cOgE3pf_BJH2Nr1eX7wiAwJyxic,6299
|
2
|
-
unittest_parametrize/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
3
|
-
unittest_parametrize-1.6.0.dist-info/LICENSE,sha256=Jn179RflkRZXhSCGt_D7asLy2Ex47C0WdBHxe0lBazk,1069
|
4
|
-
unittest_parametrize-1.6.0.dist-info/METADATA,sha256=kZJe0NHStrHyHJgkttwwmGgBOagnfB4XfffeK_SspUI,15223
|
5
|
-
unittest_parametrize-1.6.0.dist-info/WHEEL,sha256=A3WOREP4zgxI0fKrHUG8DC8013e3dK3n7a6HDbcEIwE,91
|
6
|
-
unittest_parametrize-1.6.0.dist-info/top_level.txt,sha256=4nLqNaGtBX8Ny36ZdFt37Gk-87hPtOvfdfZlTBi3OtU,21
|
7
|
-
unittest_parametrize-1.6.0.dist-info/RECORD,,
|
{unittest_parametrize-1.6.0.dist-info → unittest_parametrize-1.7.0.dist-info/licenses}/LICENSE
RENAMED
File without changes
|
File without changes
|