METADATA 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. Metadata-Version: 2.1
  2. Name: parameterized
  3. Version: 0.9.0
  4. Summary: Parameterized testing with any Python test framework
  5. Author-email: David Wolever <david@wolever.net>
  6. License: FreeBSD
  7. Project-URL: Homepage, https://github.com/wolever/parameterized
  8. Classifier: Programming Language :: Python :: 3
  9. Classifier: License :: OSI Approved :: BSD License
  10. Requires-Python: >=3.7
  11. Description-Content-Type: text/x-rst
  12. License-File: LICENSE.txt
  13. Provides-Extra: dev
  14. Requires-Dist: jinja2 ; extra == 'dev'
  15. Parameterized testing with any Python test framework
  16. ====================================================
  17. .. image:: https://img.shields.io/pypi/v/parameterized
  18. :alt: PyPI
  19. :target: https://pypi.org/project/parameterized/
  20. .. image:: https://img.shields.io/pypi/dm/parameterized
  21. :alt: PyPI - Downloads
  22. :target: https://pypi.org/project/parameterized/
  23. .. image:: https://circleci.com/gh/wolever/parameterized.svg?style=svg
  24. :alt: Circle CI
  25. :target: https://circleci.com/gh/wolever/parameterized
  26. Parameterized testing in Python sucks.
  27. ``parameterized`` fixes that. For everything. Parameterized testing for nose,
  28. parameterized testing for py.test, parameterized testing for unittest.
  29. .. code:: python
  30. # test_math.py
  31. from nose.tools import assert_equal
  32. from parameterized import parameterized, parameterized_class
  33. import unittest
  34. import math
  35. @parameterized([
  36. (2, 2, 4),
  37. (2, 3, 8),
  38. (1, 9, 1),
  39. (0, 9, 0),
  40. ])
  41. def test_pow(base, exponent, expected):
  42. assert_equal(math.pow(base, exponent), expected)
  43. class TestMathUnitTest(unittest.TestCase):
  44. @parameterized.expand([
  45. ("negative", -1.5, -2.0),
  46. ("integer", 1, 1.0),
  47. ("large fraction", 1.6, 1),
  48. ])
  49. def test_floor(self, name, input, expected):
  50. assert_equal(math.floor(input), expected)
  51. @parameterized_class(('a', 'b', 'expected_sum', 'expected_product'), [
  52. (1, 2, 3, 2),
  53. (5, 5, 10, 25),
  54. ])
  55. class TestMathClass(unittest.TestCase):
  56. def test_add(self):
  57. assert_equal(self.a + self.b, self.expected_sum)
  58. def test_multiply(self):
  59. assert_equal(self.a * self.b, self.expected_product)
  60. @parameterized_class([
  61. { "a": 3, "expected": 2 },
  62. { "b": 5, "expected": -4 },
  63. ])
  64. class TestMathClassDict(unittest.TestCase):
  65. a = 1
  66. b = 1
  67. def test_subtract(self):
  68. assert_equal(self.a - self.b, self.expected)
  69. With nose (and nose2)::
  70. $ nosetests -v test_math.py
  71. test_floor_0_negative (test_math.TestMathUnitTest) ... ok
  72. test_floor_1_integer (test_math.TestMathUnitTest) ... ok
  73. test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
  74. test_math.test_pow(2, 2, 4, {}) ... ok
  75. test_math.test_pow(2, 3, 8, {}) ... ok
  76. test_math.test_pow(1, 9, 1, {}) ... ok
  77. test_math.test_pow(0, 9, 0, {}) ... ok
  78. test_add (test_math.TestMathClass_0) ... ok
  79. test_multiply (test_math.TestMathClass_0) ... ok
  80. test_add (test_math.TestMathClass_1) ... ok
  81. test_multiply (test_math.TestMathClass_1) ... ok
  82. test_subtract (test_math.TestMathClassDict_0) ... ok
  83. ----------------------------------------------------------------------
  84. Ran 12 tests in 0.015s
  85. OK
  86. As the package name suggests, nose is best supported and will be used for all
  87. further examples.
  88. With py.test (version 2.0 and above)::
  89. $ py.test -v test_math.py
  90. ============================= test session starts ==============================
  91. platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
  92. collecting ... collected 13 items
  93. test_math.py::test_pow::[0] PASSED
  94. test_math.py::test_pow::[1] PASSED
  95. test_math.py::test_pow::[2] PASSED
  96. test_math.py::test_pow::[3] PASSED
  97. test_math.py::TestMathUnitTest::test_floor_0_negative PASSED
  98. test_math.py::TestMathUnitTest::test_floor_1_integer PASSED
  99. test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED
  100. test_math.py::TestMathClass_0::test_add PASSED
  101. test_math.py::TestMathClass_0::test_multiply PASSED
  102. test_math.py::TestMathClass_1::test_add PASSED
  103. test_math.py::TestMathClass_1::test_multiply PASSED
  104. test_math.py::TestMathClassDict_0::test_subtract PASSED
  105. ==================== 12 passed, 4 warnings in 0.16 seconds =====================
  106. With unittest (and unittest2)::
  107. $ python -m unittest -v test_math
  108. test_floor_0_negative (test_math.TestMathUnitTest) ... ok
  109. test_floor_1_integer (test_math.TestMathUnitTest) ... ok
  110. test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
  111. test_add (test_math.TestMathClass_0) ... ok
  112. test_multiply (test_math.TestMathClass_0) ... ok
  113. test_add (test_math.TestMathClass_1) ... ok
  114. test_multiply (test_math.TestMathClass_1) ... ok
  115. test_subtract (test_math.TestMathClassDict_0) ... ok
  116. ----------------------------------------------------------------------
  117. Ran 8 tests in 0.001s
  118. OK
  119. (note: because unittest does not support test decorators, only tests created
  120. with ``@parameterized.expand`` will be executed)
  121. With green::
  122. $ green test_math.py -vvv
  123. test_math
  124. TestMathClass_1
  125. . test_method_a
  126. . test_method_b
  127. TestMathClass_2
  128. . test_method_a
  129. . test_method_b
  130. TestMathClass_3
  131. . test_method_a
  132. . test_method_b
  133. TestMathUnitTest
  134. . test_floor_0_negative
  135. . test_floor_1_integer
  136. . test_floor_2_large_fraction
  137. TestMathClass_0
  138. . test_add
  139. . test_multiply
  140. TestMathClass_1
  141. . test_add
  142. . test_multiply
  143. TestMathClassDict_0
  144. . test_subtract
  145. Ran 12 tests in 0.121s
  146. OK (passes=9)
  147. Installation
  148. ------------
  149. ::
  150. $ pip install parameterized
  151. Compatibility
  152. -------------
  153. `Yes`__ (mostly).
  154. __ https://app.circleci.com/pipelines/github/wolever/parameterized?branch=master
  155. .. list-table::
  156. :header-rows: 1
  157. :stub-columns: 1
  158. * -
  159. - Py3.7
  160. - Py3.8
  161. - Py3.9
  162. - Py3.10
  163. - Py3.11
  164. - PyPy3
  165. - ``@mock.patch``
  166. * - nose
  167. - yes
  168. - yes
  169. - yes
  170. - yes
  171. - no§
  172. - no§
  173. - yes
  174. * - nose2
  175. - yes
  176. - yes
  177. - yes
  178. - yes
  179. - yes
  180. - yes
  181. - yes
  182. * - py.test 2
  183. - no*
  184. - no*
  185. - no*
  186. - no*
  187. - no*
  188. - no*
  189. - no*
  190. * - py.test 3
  191. - yes
  192. - yes
  193. - yes
  194. - yes
  195. - no*
  196. - no*
  197. - yes
  198. * - py.test 4
  199. - no**
  200. - no**
  201. - no**
  202. - no**
  203. - no**
  204. - no**
  205. - no**
  206. * - py.test fixtures
  207. - no†
  208. - no†
  209. - no†
  210. - no†
  211. - no†
  212. - no†
  213. - no†
  214. * - | unittest
  215. | (``@parameterized.expand``)
  216. - yes
  217. - yes
  218. - yes
  219. - yes
  220. - yes
  221. - yes
  222. - yes
  223. * - | unittest2
  224. | (``@parameterized.expand``)
  225. - yes
  226. - yes
  227. - yes
  228. - yes
  229. - no§
  230. - no§
  231. - yes
  232. §: nose and unittest2 - both of which were last updated in 2015 - sadly do not
  233. appear to support Python 3.10 or 3.11.
  234. \*: `py.test 2 does not appear to work under Python 3 (#71)`__, and
  235. `py.test 3 does not appear to work under Python 3.10 or 3.11 (#154)`__.
  236. \*\*: py.test 4 is not yet supported (but coming!) in `issue #34`__
  237. †: py.test fixture support is documented in `issue #81`__
  238. __ https://github.com/wolever/parameterized/issues/71
  239. __ https://github.com/wolever/parameterized/issues/154
  240. __ https://github.com/wolever/parameterized/issues/34
  241. __ https://github.com/wolever/parameterized/issues/81
  242. Dependencies
  243. ------------
  244. (this section left intentionally blank)
  245. Exhaustive Usage Examples
  246. --------------------------
  247. The ``@parameterized`` and ``@parameterized.expand`` decorators accept a list
  248. or iterable of tuples or ``param(...)``, or a callable which returns a list or
  249. iterable:
  250. .. code:: python
  251. from parameterized import parameterized, param
  252. # A list of tuples
  253. @parameterized([
  254. (2, 3, 5),
  255. (3, 5, 8),
  256. ])
  257. def test_add(a, b, expected):
  258. assert_equal(a + b, expected)
  259. # A list of params
  260. @parameterized([
  261. param("10", 10),
  262. param("10", 16, base=16),
  263. ])
  264. def test_int(str_val, expected, base=10):
  265. assert_equal(int(str_val, base=base), expected)
  266. # An iterable of params
  267. @parameterized(
  268. param.explicit(*json.loads(line))
  269. for line in open("testcases.jsons")
  270. )
  271. def test_from_json_file(...):
  272. ...
  273. # A callable which returns a list of tuples
  274. def load_test_cases():
  275. return [
  276. ("test1", ),
  277. ("test2", ),
  278. ]
  279. @parameterized(load_test_cases)
  280. def test_from_function(name):
  281. ...
  282. .. **
  283. Note that, when using an iterator or a generator, all the items will be loaded
  284. into memory before the start of the test run (we do this explicitly to ensure
  285. that generators are exhausted exactly once in multi-process or multi-threaded
  286. testing environments).
  287. The ``@parameterized`` decorator can be used test class methods, and standalone
  288. functions:
  289. .. code:: python
  290. from parameterized import parameterized
  291. class AddTest(object):
  292. @parameterized([
  293. (2, 3, 5),
  294. ])
  295. def test_add(self, a, b, expected):
  296. assert_equal(a + b, expected)
  297. @parameterized([
  298. (2, 3, 5),
  299. ])
  300. def test_add(a, b, expected):
  301. assert_equal(a + b, expected)
  302. And ``@parameterized.expand`` can be used to generate test methods in
  303. situations where test generators cannot be used (for example, when the test
  304. class is a subclass of ``unittest.TestCase``):
  305. .. code:: python
  306. import unittest
  307. from parameterized import parameterized
  308. class AddTestCase(unittest.TestCase):
  309. @parameterized.expand([
  310. ("2 and 3", 2, 3, 5),
  311. ("3 and 5", 3, 5, 8),
  312. ])
  313. def test_add(self, _, a, b, expected):
  314. assert_equal(a + b, expected)
  315. Will create the test cases::
  316. $ nosetests example.py
  317. test_add_0_2_and_3 (example.AddTestCase) ... ok
  318. test_add_1_3_and_5 (example.AddTestCase) ... ok
  319. ----------------------------------------------------------------------
  320. Ran 2 tests in 0.001s
  321. OK
  322. Note that ``@parameterized.expand`` works by creating new methods on the test
  323. class. If the first parameter is a string, that string will be added to the end
  324. of the method name. For example, the test case above will generate the methods
  325. ``test_add_0_2_and_3`` and ``test_add_1_3_and_5``.
  326. The names of the test cases generated by ``@parameterized.expand`` can be
  327. customized using the ``name_func`` keyword argument. The value should
  328. be a function which accepts three arguments: ``testcase_func``, ``param_num``,
  329. and ``params``, and it should return the name of the test case.
  330. ``testcase_func`` will be the function to be tested, ``param_num`` will be the
  331. index of the test case parameters in the list of parameters, and ``param``
  332. (an instance of ``param``) will be the parameters which will be used.
  333. .. code:: python
  334. import unittest
  335. from parameterized import parameterized
  336. def custom_name_func(testcase_func, param_num, param):
  337. return "%s_%s" %(
  338. testcase_func.__name__,
  339. parameterized.to_safe_name("_".join(str(x) for x in param.args)),
  340. )
  341. class AddTestCase(unittest.TestCase):
  342. @parameterized.expand([
  343. (2, 3, 5),
  344. (2, 3, 5),
  345. ], name_func=custom_name_func)
  346. def test_add(self, a, b, expected):
  347. assert_equal(a + b, expected)
  348. Will create the test cases::
  349. $ nosetests example.py
  350. test_add_1_2_3 (example.AddTestCase) ... ok
  351. test_add_2_3_5 (example.AddTestCase) ... ok
  352. ----------------------------------------------------------------------
  353. Ran 2 tests in 0.001s
  354. OK
  355. The ``param(...)`` helper class stores the parameters for one specific test
  356. case. It can be used to pass keyword arguments to test cases:
  357. .. code:: python
  358. from parameterized import parameterized, param
  359. @parameterized([
  360. param("10", 10),
  361. param("10", 16, base=16),
  362. ])
  363. def test_int(str_val, expected, base=10):
  364. assert_equal(int(str_val, base=base), expected)
  365. If test cases have a docstring, the parameters for that test case will be
  366. appended to the first line of the docstring. This behavior can be controlled
  367. with the ``doc_func`` argument:
  368. .. code:: python
  369. from parameterized import parameterized
  370. @parameterized([
  371. (1, 2, 3),
  372. (4, 5, 9),
  373. ])
  374. def test_add(a, b, expected):
  375. """ Test addition. """
  376. assert_equal(a + b, expected)
  377. def my_doc_func(func, num, param):
  378. return "%s: %s with %s" %(num, func.__name__, param)
  379. @parameterized([
  380. (5, 4, 1),
  381. (9, 6, 3),
  382. ], doc_func=my_doc_func)
  383. def test_subtraction(a, b, expected):
  384. assert_equal(a - b, expected)
  385. ::
  386. $ nosetests example.py
  387. Test addition. [with a=1, b=2, expected=3] ... ok
  388. Test addition. [with a=4, b=5, expected=9] ... ok
  389. 0: test_subtraction with param(*(5, 4, 1)) ... ok
  390. 1: test_subtraction with param(*(9, 6, 3)) ... ok
  391. ----------------------------------------------------------------------
  392. Ran 4 tests in 0.001s
  393. OK
  394. Finally ``@parameterized_class`` parameterizes an entire class, using
  395. either a list of attributes, or a list of dicts that will be applied to the
  396. class:
  397. .. code:: python
  398. from yourapp.models import User
  399. from parameterized import parameterized_class
  400. @parameterized_class([
  401. { "username": "user_1", "access_level": 1 },
  402. { "username": "user_2", "access_level": 2, "expected_status_code": 404 },
  403. ])
  404. class TestUserAccessLevel(TestCase):
  405. expected_status_code = 200
  406. def setUp(self):
  407. self.client.force_login(User.objects.get(username=self.username)[0])
  408. def test_url_a(self):
  409. response = self.client.get('/url')
  410. self.assertEqual(response.status_code, self.expected_status_code)
  411. def tearDown(self):
  412. self.client.logout()
  413. @parameterized_class(("username", "access_level", "expected_status_code"), [
  414. ("user_1", 1, 200),
  415. ("user_2", 2, 404)
  416. ])
  417. class TestUserAccessLevel(TestCase):
  418. def setUp(self):
  419. self.client.force_login(User.objects.get(username=self.username)[0])
  420. def test_url_a(self):
  421. response = self.client.get("/url")
  422. self.assertEqual(response.status_code, self.expected_status_code)
  423. def tearDown(self):
  424. self.client.logout()
  425. The ``@parameterized_class`` decorator accepts a ``class_name_func`` argument,
  426. which controls the name of the parameterized classes generated by
  427. ``@parameterized_class``:
  428. .. code:: python
  429. from parameterized import parameterized, parameterized_class
  430. def get_class_name(cls, num, params_dict):
  431. # By default the generated class named includes either the "name"
  432. # parameter (if present), or the first string value. This example shows
  433. # multiple parameters being included in the generated class name:
  434. return "%s_%s_%s%s" %(
  435. cls.__name__,
  436. num,
  437. parameterized.to_safe_name(params_dict['a']),
  438. parameterized.to_safe_name(params_dict['b']),
  439. )
  440. @parameterized_class([
  441. { "a": "hello", "b": " world!", "expected": "hello world!" },
  442. { "a": "say ", "b": " cheese :)", "expected": "say cheese :)" },
  443. ], class_name_func=get_class_name)
  444. class TestConcatenation(TestCase):
  445. def test_concat(self):
  446. self.assertEqual(self.a + self.b, self.expected)
  447. ::
  448. $ nosetests -v test_math.py
  449. test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok
  450. test_concat (test_concat.TestConcatenation_0_say_cheese__) ... ok
  451. Using with Single Parameters
  452. ............................
  453. If a test function only accepts one parameter and the value is not iterable,
  454. then it is possible to supply a list of values without wrapping each one in a
  455. tuple:
  456. .. code:: python
  457. @parameterized([1, 2, 3])
  458. def test_greater_than_zero(value):
  459. assert value > 0
  460. Note, however, that if the single parameter *is* iterable (such as a list or
  461. tuple), then it *must* be wrapped in a tuple, list, or the ``param(...)``
  462. helper:
  463. .. code:: python
  464. @parameterized([
  465. ([1, 2, 3], ),
  466. ([3, 3], ),
  467. ([6], ),
  468. ])
  469. def test_sums_to_6(numbers):
  470. assert sum(numbers) == 6
  471. (note, also, that Python requires single element tuples to be defined with a
  472. trailing comma: ``(foo, )``)
  473. Using with ``@mock.patch``
  474. ..........................
  475. ``parameterized`` can be used with ``mock.patch``, but the argument ordering
  476. can be confusing. The ``@mock.patch(...)`` decorator must come *below* the
  477. ``@parameterized(...)``, and the mocked parameters must come *last*:
  478. .. code:: python
  479. @mock.patch("os.getpid")
  480. class TestOS(object):
  481. @parameterized(...)
  482. @mock.patch("os.fdopen")
  483. @mock.patch("os.umask")
  484. def test_method(self, param1, param2, ..., mock_umask, mock_fdopen, mock_getpid):
  485. ...
  486. Note: the same holds true when using ``@parameterized.expand``.
  487. Migrating from ``nose-parameterized`` to ``parameterized``
  488. ----------------------------------------------------------
  489. To migrate a codebase from ``nose-parameterized`` to ``parameterized``:
  490. 1. Update your requirements file, replacing ``nose-parameterized`` with
  491. ``parameterized``.
  492. 2. Replace all references to ``nose_parameterized`` with ``parameterized``::
  493. $ perl -pi -e 's/nose_parameterized/parameterized/g' your-codebase/
  494. 3. You're done!
  495. FAQ
  496. ---
  497. What happened to Python 2.X, 3.5, and 3.6 support?
  498. As of version 0.9.0, ``parameterized`` no longer supports Python 2.X, 3.5,
  499. or 3.6. Previous versions of ``parameterized`` - 0.8.1 being the latest -
  500. will continue to work, but will not receive any new features or bug fixes.
  501. What do you mean when you say "nose is best supported"?
  502. There are small caveates with ``py.test`` and ``unittest``: ``py.test``
  503. does not show the parameter values (ex, it will show ``test_add[0]``
  504. instead of ``test_add[1, 2, 3]``), and ``unittest``/``unittest2`` do not
  505. support test generators so ``@parameterized.expand`` must be used.
  506. Why not use ``@pytest.mark.parametrize``?
  507. Because spelling is difficult. Also, ``parameterized`` doesn't require you
  508. to repeat argument names, and (using ``param``) it supports optional
  509. keyword arguments.
  510. Why do I get an ``AttributeError: 'function' object has no attribute 'expand'`` with ``@parameterized.expand``?
  511. You've likely installed the ``parametrized`` (note the missing *e*)
  512. package. Use ``parameterized`` (with the *e*) instead and you'll be all
  513. set.
  514. What happened to ``nose-parameterized``?
  515. Originally only nose was supported. But now everything is supported, and it
  516. only made sense to change the name!