unittest-parametrize 1.0.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.
@@ -0,0 +1,8 @@
1
+ =========
2
+ Changelog
3
+ =========
4
+
5
+ 1.0.0 (2023-03-28)
6
+ ------------------
7
+
8
+ * First release.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Adam Johnson
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,5 @@
1
+ include CHANGELOG.rst
2
+ include LICENSE
3
+ include pyproject.toml
4
+ include README.rst
5
+ include src/*/py.typed
@@ -0,0 +1,359 @@
1
+ Metadata-Version: 2.1
2
+ Name: unittest_parametrize
3
+ Version: 1.0.0
4
+ Summary: Parametrize tests within unittest TestCases.
5
+ Home-page: https://github.com/adamchainz/unittest-parametrize
6
+ Author: Adam Johnson
7
+ Author-email: me@adamj.eu
8
+ License: MIT
9
+ Project-URL: Changelog, https://github.com/adamchainz/unittest-parametrize/blob/main/CHANGELOG.rst
10
+ Project-URL: Mastodon, https://fosstodon.org/@adamchainz
11
+ Project-URL: Twitter, https://twitter.com/adamchainz
12
+ Keywords: unittest
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Natural Language :: English
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Programming Language :: Python :: 3.7
20
+ Classifier: Programming Language :: Python :: 3.8
21
+ Classifier: Programming Language :: Python :: 3.9
22
+ Classifier: Programming Language :: Python :: 3.10
23
+ Classifier: Programming Language :: Python :: 3.11
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.7
26
+ Description-Content-Type: text/x-rst
27
+ License-File: LICENSE
28
+
29
+ ====================
30
+ unittest-parametrize
31
+ ====================
32
+
33
+ .. image:: https://img.shields.io/github/actions/workflow/status/adamchainz/unittest-parametrize/main.yml?branch=main&style=for-the-badge
34
+ :target: https://github.com/adamchainz/unittest-parametrize/actions?workflow=CI
35
+
36
+ .. image:: https://img.shields.io/pypi/v/unittest-parametrize.svg?style=for-the-badge
37
+ :target: https://pypi.org/project/unittest-parametrize/
38
+
39
+ .. image:: https://img.shields.io/badge/code%20style-black-000000.svg?style=for-the-badge
40
+ :target: https://github.com/psf/black
41
+
42
+ .. image:: https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white&style=for-the-badge
43
+ :target: https://github.com/pre-commit/pre-commit
44
+ :alt: pre-commit
45
+
46
+ Parametrize tests within unittest TestCases.
47
+
48
+ Installation
49
+ ============
50
+
51
+ Install with:
52
+
53
+ .. code-block:: bash
54
+
55
+ python -m pip install unittest-parametrize
56
+
57
+ Python 3.7 to 3.11 supported.
58
+
59
+ ----
60
+
61
+ **Testing a Django project?**
62
+ Check out my book `Speed Up Your Django Tests <https://adamchainz.gumroad.com/l/suydt>`__ which covers loads of recommendations to write faster, more accurate tests.
63
+
64
+ ----
65
+
66
+ Usage
67
+ =====
68
+
69
+ The API mirrors |@pytest.mark.parametrize|__ as much as possible.
70
+ (Even the name `parametrize <https://en.wiktionary.org/wiki/parametrize#English>`__ over the slightly more common `parameterize <https://en.wiktionary.org/wiki/parameterize#English>`__ with an extra “e”.
71
+ Don’t get caught out by that…)
72
+
73
+ .. |@pytest.mark.parametrize| replace:: ``@pytest.mark.parametrize``
74
+ __ https://docs.pytest.org/en/stable/how-to/parametrize.html#parametrize-basics
75
+
76
+ There are two steps to parametrize a test case:
77
+
78
+ 1. Use ``ParametrizedTestCase`` in the base classes for your test case.
79
+ 2. Apply ``@parametrize`` to any tests for parametrization.
80
+ This decorator takes (at least):
81
+
82
+ * the argument names to parametrize, as comma-separated string
83
+ * a list of parameter tuples to create individual tests for
84
+
85
+ Here’s a basic example:
86
+
87
+ .. code-block:: python
88
+
89
+ from unittest_parametrize import parametrize
90
+ from unittest_parametrize import ParametrizedTestCase
91
+
92
+
93
+ class SquareTests(ParametrizedTestCase):
94
+ @parametrize(
95
+ "x,expected",
96
+ [
97
+ (1, 1),
98
+ (2, 4),
99
+ ],
100
+ )
101
+ def test_square(self, x: int, expected: int) -> None:
102
+ self.assertEqual(x**2, expected)
103
+
104
+ ``@parametrize`` modifies the class at definition time with Python’s |__init_subclass__ hook|__.
105
+ It removes the original test method and creates wrapped copies with individual names.
106
+ Thus the parametrization should work regardless of the test runner you use (be it unittest, Django’s test runner, pytest, etc.).
107
+
108
+ .. |__init_subclass__ hook| replace:: ``__init_subclass__`` hook
109
+ __ https://docs.python.org/3/reference/datamodel.html#object.__init_subclass__
110
+
111
+ Provide argument names as a string
112
+ ----------------------------------
113
+
114
+ If you need, you can provide argument names as a sequence of strings instead:
115
+
116
+ .. code-block:: python
117
+
118
+ from unittest_parametrize import parametrize
119
+ from unittest_parametrize import ParametrizedTestCase
120
+
121
+
122
+ class SquareTests(ParametrizedTestCase):
123
+ @parametrize(
124
+ ("x", "expected"),
125
+ [
126
+ (1, 1),
127
+ (2, 4),
128
+ ],
129
+ )
130
+ def test_square(self, x: int, expected: int) -> None:
131
+ self.assertEqual(x**2, expected)
132
+
133
+ Custom test name suffixes
134
+ -------------------------
135
+
136
+ By default, test names are extended with an index, starting at zero.
137
+ You can see these names when running the tests:
138
+
139
+ .. code-block:: console
140
+
141
+ $ python -m unittest t.py -v
142
+ test_square_0 (t.SquareTests.test_square_0) ... ok
143
+ test_square_1 (t.SquareTests.test_square_1) ... ok
144
+
145
+ ----------------------------------------------------------------------
146
+ Ran 2 tests in 0.000s
147
+
148
+ OK
149
+
150
+ You can customize these names by passing ``param`` objects, which contain the arguments plus an ID for the suffix:
151
+
152
+ .. code-block:: python
153
+
154
+ from unittest_parametrize import param
155
+ from unittest_parametrize import parametrize
156
+ from unittest_parametrize import ParametrizedTestCase
157
+
158
+
159
+ class SquareTests(ParametrizedTestCase):
160
+ @parametrize(
161
+ "x,expected",
162
+ [
163
+ param(1, 1, id="one"),
164
+ param(2, 4, id="two"),
165
+ ],
166
+ )
167
+ def test_square(self, x: int, expected: int) -> None:
168
+ self.assertEqual(x**2, expected)
169
+
170
+ Yielding perhaps more natural names:
171
+
172
+ .. code-block:: console
173
+
174
+ $ python -m unittest t.py -v
175
+ test_square_one (t.SquareTests.test_square_one) ... ok
176
+ test_square_two (t.SquareTests.test_square_two) ... ok
177
+
178
+ ----------------------------------------------------------------------
179
+ Ran 2 tests in 0.000s
180
+
181
+ OK
182
+
183
+ Parameter IDs should be valid Python identifier suffixes.
184
+
185
+ Alternatively, you can provide the id’s separately with the ``ids`` argument:
186
+
187
+ .. code-block:: python
188
+
189
+ from unittest_parametrize import parametrize
190
+ from unittest_parametrize import ParametrizedTestCase
191
+
192
+
193
+ class SquareTests(ParametrizedTestCase):
194
+ @parametrize(
195
+ "x,expected",
196
+ [
197
+ (1, 1),
198
+ (2, 4),
199
+ ],
200
+ ids=["one", "two"],
201
+ )
202
+ def test_square(self, x: int, expected: int) -> None:
203
+ self.assertEqual(x**2, expected)
204
+
205
+ Use with other test decorators
206
+ ------------------------------
207
+
208
+ ``@parametrize`` tries to ensure it is the top-most (outermost) decorator.
209
+ This limitation exists to ensure that the decorator applies to each test.
210
+ So decorators like ``@mock.patch.object`` need be beneath ``@parametrize``:
211
+
212
+ .. code-block:: python
213
+
214
+ from unittest import mock
215
+ from unittest_parametrize import parametrize
216
+ from unittest_parametrize import ParametrizedTestCase
217
+
218
+
219
+ class MockingTests(ParametrizedTestCase):
220
+ @parametrize(
221
+ "nails",
222
+ [(1,), (2,)],
223
+ )
224
+ @mock.patch.object(board, "length", new=9001)
225
+ def test_boarding(self, nails):
226
+ ...
227
+
228
+ Multiple ``@parametrize`` decorators
229
+ ------------------------------------
230
+
231
+ ``@parametrize`` is not stackable.
232
+ To create a cross-product of tests, use |itertools.product()|__:
233
+
234
+ .. |itertools.product()| replace:: ``itertools.product()``
235
+ __ https://docs.python.org/3/library/itertools.html#itertools.product
236
+
237
+ .. code-block:: python
238
+
239
+ from itertools import product
240
+ from unittest_parametrize import parametrize
241
+ from unittest_parametrize import ParametrizedTestCase
242
+
243
+
244
+ class RocketTests(ParametrizedTestCase):
245
+ @parametrize(
246
+ "use_ions,hyperdrive_level,nose_colour",
247
+ list(
248
+ product(
249
+ [True, False],
250
+ [0, 1, 2],
251
+ ["red", "yellow"],
252
+ )
253
+ ),
254
+ )
255
+ def test_takeoff(self, use_ions, hyperdrive_level, nose_colour) -> None:
256
+ ...
257
+
258
+ The above creates 2 * 3 * 2 = 12 versions of ``test_takeoff``.
259
+
260
+ Use ``ParametrizedTestCase`` in your base test case class
261
+ ---------------------------------------------------------
262
+
263
+ ``ParametrizedTestCase`` does nothing if there aren’t any ``@parametrize``-decorated tests within a class.
264
+ Therefore you can include it in your project’s base test case class so that ``@parametrize`` works immediately in all test cases.
265
+
266
+ For example, within a Django project, you can create a set of project-specific base test case classes extending `those provided by Django <https://docs.djangoproject.com/en/stable/topics/testing/tools/#provided-test-case-classes>`__.
267
+ You can do this in a module like ``example.test``, and use the base classes throughout your test suite.
268
+ To add ``ParametrizedTestCase`` to all your copies, use it in a custom ``SimpleTestCase`` and then mixin to others using multiple inheritance like so:
269
+
270
+ .. code-block:: python
271
+
272
+ from django import test
273
+ from unittest_parametrize import ParametrizedTestCase
274
+
275
+
276
+ class SimpleTestCase(ParametrizedTestCase, test.SimpleTestCase):
277
+ pass
278
+
279
+
280
+ class TestCase(SimpleTestCase, test.TestCase):
281
+ pass
282
+
283
+
284
+ class TransactionTestCase(SimpleTestCase, test.TransactionTestCase):
285
+ pass
286
+
287
+
288
+ class LiveServerTestCase(SimpleTestCase, test.LiveServerTestCase):
289
+ pass
290
+
291
+ History
292
+ =======
293
+
294
+ When I started writing unit tests, I learned to use `DDT (Data-Driven Tests) <https://ddt.readthedocs.io/en/latest/>`__ for parametrizing tests.
295
+ It works, but the docs are a bit thin, and the API a little obscure (what does ``@ddt`` stand for again?).
296
+
297
+ Later when picking up pytest, I learned to use its `parametrization API <https://docs.pytest.org/en/stable/how-to/parametrize.html>`__.
298
+ It’s legible and flexible, but it doesn’t work with unittest test cases, which Django’s test tooling provides.
299
+
300
+ So, until the creation of this package, I was using `parameterized <https://pypi.org/project/parameterized/>`__ on my (Django) test cases.
301
+ This package supports parametrization across multiple test runners, though most of them are “legacy” by now.
302
+
303
+ I created unittest-parametrize as a smaller alternative to *parameterized*, with these goals:
304
+
305
+ 1. Only support unittest test cases.
306
+ For other types of test, you can use pytest’s parametrization.
307
+
308
+ 2. Avoid any custom test runner support.
309
+ Modifying the class at definition time means that all test runners will see the tests the same.
310
+
311
+ 3. Use modern Python features like ``__init_subclass__``.
312
+
313
+ 4. Have full type hint coverage.
314
+ You shouldn’t find unittest-parametrize a blocker when adopting Mypy with strict mode on.
315
+
316
+ 5. Use the name “parametrize” rather than “parameterize”.
317
+ This unification of spelling with pytest should help reduce confusion around the extra “e”.
318
+
319
+ Thanks to the creators and maintainers of ddt, parameterized, and pytest for their hard work.
320
+
321
+ Why not subtests?
322
+ -----------------
323
+
324
+ |TestCase.subTest()|__ is unittest’s built-in “parametrization” solution.
325
+ You use it in a loop within a single test method:
326
+
327
+ .. |TestCase.subTest()| replace:: ``TestCase.subTest()``
328
+ __ https://docs.python.org/3/library/unittest.html#unittest.TestCase.subTest
329
+
330
+ .. code-block:: python
331
+
332
+ from unittest import TestCase
333
+
334
+
335
+ class SquareTests(TestCase):
336
+ def test_square(self):
337
+ tests = [
338
+ (1, 1),
339
+ (2, 4),
340
+ ]
341
+ for x, expected in tests:
342
+ with self.subTest(x=x):
343
+ self.assertEqual(x**2, expected)
344
+
345
+ This approach crams multiple actual tests into one test method, with several consequences:
346
+
347
+ * If a subtest fails, it prevents the next subtests from running.
348
+ Thus, failures are harder to debug, since each test run can only give you partial information.
349
+
350
+ * Subtests can leak state.
351
+ Without correct isolation, they may not test what they appear to.
352
+
353
+ * Subtests cannot be reordered by tools that detect state leakage, like `pytest-randomly <https://github.com/pytest-dev/pytest-randomly>`__.
354
+
355
+ * Subtests skew test timings, since the test method runs multiple tests.
356
+
357
+ * Everything is indented two extra levels for the loop and context manager.
358
+
359
+ Parametrization avoids all these issues by creating individual test methods.