README.rst 18 KB

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