README.rst 18 KB

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