Package: parameterized
Parameterized testing in Python sucks.parameterizedfixes that. For everything. Parameterized testing for nose,
parameterized testing for py.test, parameterized testing for unittest.# test_math.pyfromnose.toolsimportassert_equalfromparameterizedimportparameterized,parameterized_classimportunittestimportmath@parameterized([(2,2,4),(2,3,8),(1,9,1),(0,9,0),])deftest_pow(base,exponent,expected):assert_equal(math.pow(base,exponent),expected)classTestMathUnitTest(unittest.TestCase):@parameterized.expand([("negative",-1.5,-2.0),("integer",1,1.0),("large fraction",1.6,1),])deftest_floor(self,name,input,expected):assert_equal(math.floor(input),expected)@parameterized_class(('a','b','expected_sum','expected_product'),[(1,2,3,2),(5,5,10,25),])classTestMathClass(unittest.TestCase):deftest_add(self):assert_equal(self.a+self.b,self.expected_sum)deftest_multiply(self):assert_equal(self.a*self.b,self.expected_product)@parameterized_class([{"a":3,"expected":2},{"b":5,"expected":-4},])classTestMathClassDict(unittest.TestCase):a=1b=1deftest_subtract(self):assert_equal(self.a-self.b,self.expected)With nose (and nose2):$ nosetests -v test_math.py
test_floor_0_negative (test_math.TestMathUnitTest) ... ok
test_floor_1_integer (test_math.TestMathUnitTest) ... ok
test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
test_math.test_pow(2, 2, 4, {}) ... ok
test_math.test_pow(2, 3, 8, {}) ... ok
test_math.test_pow(1, 9, 1, {}) ... ok
test_math.test_pow(0, 9, 0, {}) ... ok
test_add (test_math.TestMathClass_0) ... ok
test_multiply (test_math.TestMathClass_0) ... ok
test_add (test_math.TestMathClass_1) ... ok
test_multiply (test_math.TestMathClass_1) ... ok
test_subtract (test_math.TestMathClassDict_0) ... ok

----------------------------------------------------------------------
Ran 12 tests in 0.015s

OKAs the package name suggests, nose is best supported and will be used for all
further examples.With py.test (version 2.0 and above):$ py.test -v test_math.py
============================= test session starts ==============================
platform darwin -- Python 3.6.1, pytest-3.1.3, py-1.4.34, pluggy-0.4.0
collecting ... collected 13 items

test_math.py::test_pow::[0] PASSED
test_math.py::test_pow::[1] PASSED
test_math.py::test_pow::[2] PASSED
test_math.py::test_pow::[3] PASSED
test_math.py::TestMathUnitTest::test_floor_0_negative PASSED
test_math.py::TestMathUnitTest::test_floor_1_integer PASSED
test_math.py::TestMathUnitTest::test_floor_2_large_fraction PASSED
test_math.py::TestMathClass_0::test_add PASSED
test_math.py::TestMathClass_0::test_multiply PASSED
test_math.py::TestMathClass_1::test_add PASSED
test_math.py::TestMathClass_1::test_multiply PASSED
test_math.py::TestMathClassDict_0::test_subtract PASSED
==================== 12 passed, 4 warnings in 0.16 seconds =====================With unittest (and unittest2):$ python -m unittest -v test_math
test_floor_0_negative (test_math.TestMathUnitTest) ... ok
test_floor_1_integer (test_math.TestMathUnitTest) ... ok
test_floor_2_large_fraction (test_math.TestMathUnitTest) ... ok
test_add (test_math.TestMathClass_0) ... ok
test_multiply (test_math.TestMathClass_0) ... ok
test_add (test_math.TestMathClass_1) ... ok
test_multiply (test_math.TestMathClass_1) ... ok
test_subtract (test_math.TestMathClassDict_0) ... ok

----------------------------------------------------------------------
Ran 8 tests in 0.001s

OK(note: because unittest does not support test decorators, only tests created
with@parameterized.expandwill be executed)With green:$ green test_math.py -vvv
test_math
  TestMathClass_1
.   test_method_a
.   test_method_b
  TestMathClass_2
.   test_method_a
.   test_method_b
  TestMathClass_3
.   test_method_a
.   test_method_b
  TestMathUnitTest
.   test_floor_0_negative
.   test_floor_1_integer
.   test_floor_2_large_fraction
  TestMathClass_0
.   test_add
.   test_multiply
  TestMathClass_1
.   test_add
.   test_multiply
  TestMathClassDict_0
.   test_subtract

Ran 12 tests in 0.121s

OK (passes=9)Installation$ pip install parameterizedCompatibilityYes(mostly).Py3.7Py3.8Py3.9Py3.10Py3.11PyPy3@mock.patchnoseyesyesyesyesno§no§yesnose2yesyesyesyesyesyesyespy.test 2no*no*no*no*no*no*no*py.test 3yesyesyesyesno*no*yespy.test 4no**no**no**no**no**no**no**py.test fixturesno†no†no†no†no†no†no†unittest(@parameterized.expand)yesyesyesyesyesyesyesunittest2(@parameterized.expand)yesyesyesyesno§no§yes§: nose and unittest2 - both of which were last updated in 2015 - sadly do not
appear to support Python 3.10 or 3.11.*:py.test 2 does not appear to work under Python 3 (#71), andpy.test 3 does not appear to work under Python 3.10 or 3.11 (#154).**: py.test 4 is not yet supported (but coming!) inissue #34†: py.test fixture support is documented inissue #81Dependencies(this section left intentionally blank)Exhaustive Usage ExamplesThe@parameterizedand@parameterized.expanddecorators accept a list
or iterable of tuples orparam(...), or a callable which returns a list or
iterable:fromparameterizedimportparameterized,param# A list of tuples@parameterized([(2,3,5),(3,5,8),])deftest_add(a,b,expected):assert_equal(a+b,expected)# A list of params@parameterized([param("10",10),param("10",16,base=16),])deftest_int(str_val,expected,base=10):assert_equal(int(str_val,base=base),expected)# An iterable of params@parameterized(param.explicit(*json.loads(line))forlineinopen("testcases.jsons"))deftest_from_json_file(...):...# A callable which returns a list of tuplesdefload_test_cases():return[("test1",),("test2",),]@parameterized(load_test_cases)deftest_from_function(name):...Note that, when using an iterator or a generator, all the items will be loaded
into memory before the start of the test run (we do this explicitly to ensure
that generators are exhausted exactly once in multi-process or multi-threaded
testing environments).The@parameterizeddecorator can be used test class methods, and standalone
functions:fromparameterizedimportparameterizedclassAddTest(object):@parameterized([(2,3,5),])deftest_add(self,a,b,expected):assert_equal(a+b,expected)@parameterized([(2,3,5),])deftest_add(a,b,expected):assert_equal(a+b,expected)And@parameterized.expandcan be used to generate test methods in
situations where test generators cannot be used (for example, when the test
class is a subclass ofunittest.TestCase):importunittestfromparameterizedimportparameterizedclassAddTestCase(unittest.TestCase):@parameterized.expand([("2 and 3",2,3,5),("3 and 5",3,5,8),])deftest_add(self,_,a,b,expected):assert_equal(a+b,expected)Will create the test cases:$ nosetests example.py
test_add_0_2_and_3 (example.AddTestCase) ... ok
test_add_1_3_and_5 (example.AddTestCase) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OKNote that@parameterized.expandworks by creating new methods on the test
class. If the first parameter is a string, that string will be added to the end
of the method name. For example, the test case above will generate the methodstest_add_0_2_and_3andtest_add_1_3_and_5.The names of the test cases generated by@parameterized.expandcan be
customized using thename_funckeyword argument. The value should
be a function which accepts three arguments:testcase_func,param_num,
andparams, and it should return the name of the test case.testcase_funcwill be the function to be tested,param_numwill be the
index of the test case parameters in the list of parameters, andparam(an instance ofparam) will be the parameters which will be used.importunittestfromparameterizedimportparameterizeddefcustom_name_func(testcase_func,param_num,param):return"%s_%s"%(testcase_func.__name__,parameterized.to_safe_name("_".join(str(x)forxinparam.args)),)classAddTestCase(unittest.TestCase):@parameterized.expand([(2,3,5),(2,3,5),],name_func=custom_name_func)deftest_add(self,a,b,expected):assert_equal(a+b,expected)Will create the test cases:$ nosetests example.py
test_add_1_2_3 (example.AddTestCase) ... ok
test_add_2_3_5 (example.AddTestCase) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.001s

OKTheparam(...)helper class stores the parameters for one specific test
case.  It can be used to pass keyword arguments to test cases:fromparameterizedimportparameterized,param@parameterized([param("10",10),param("10",16,base=16),])deftest_int(str_val,expected,base=10):assert_equal(int(str_val,base=base),expected)If test cases have a docstring, the parameters for that test case will be
appended to the first line of the docstring. This behavior can be controlled
with thedoc_funcargument:fromparameterizedimportparameterized@parameterized([(1,2,3),(4,5,9),])deftest_add(a,b,expected):""" Test addition. """assert_equal(a+b,expected)defmy_doc_func(func,num,param):return"%s:%swith%s"%(num,func.__name__,param)@parameterized([(5,4,1),(9,6,3),],doc_func=my_doc_func)deftest_subtraction(a,b,expected):assert_equal(a-b,expected)$ nosetests example.py
Test addition. [with a=1, b=2, expected=3] ... ok
Test addition. [with a=4, b=5, expected=9] ... ok
0: test_subtraction with param(*(5, 4, 1)) ... ok
1: test_subtraction with param(*(9, 6, 3)) ... ok

----------------------------------------------------------------------
Ran 4 tests in 0.001s

OKFinally@parameterized_classparameterizes an entire class, using
either a list of attributes, or a list of dicts that will be applied to the
class:fromyourapp.modelsimportUserfromparameterizedimportparameterized_class@parameterized_class([{"username":"user_1","access_level":1},{"username":"user_2","access_level":2,"expected_status_code":404},])classTestUserAccessLevel(TestCase):expected_status_code=200defsetUp(self):self.client.force_login(User.objects.get(username=self.username)[0])deftest_url_a(self):response=self.client.get('/url')self.assertEqual(response.status_code,self.expected_status_code)deftearDown(self):self.client.logout()@parameterized_class(("username","access_level","expected_status_code"),[("user_1",1,200),("user_2",2,404)])classTestUserAccessLevel(TestCase):defsetUp(self):self.client.force_login(User.objects.get(username=self.username)[0])deftest_url_a(self):response=self.client.get("/url")self.assertEqual(response.status_code,self.expected_status_code)deftearDown(self):self.client.logout()The@parameterized_classdecorator accepts aclass_name_funcargument,
which controls the name of the parameterized classes generated by@parameterized_class:fromparameterizedimportparameterized,parameterized_classdefget_class_name(cls,num,params_dict):# By default the generated class named includes either the "name"# parameter (if present), or the first string value. This example shows# multiple parameters being included in the generated class name:return"%s_%s_%s%s"%(cls.__name__,num,parameterized.to_safe_name(params_dict['a']),parameterized.to_safe_name(params_dict['b']),)@parameterized_class([{"a":"hello","b":" world!","expected":"hello world!"},{"a":"say ","b":" cheese :)","expected":"say cheese :)"},],class_name_func=get_class_name)classTestConcatenation(TestCase):deftest_concat(self):self.assertEqual(self.a+self.b,self.expected)$ nosetests -v test_math.py
test_concat (test_concat.TestConcatenation_0_hello_world_) ... ok
test_concat (test_concat.TestConcatenation_0_say_cheese__) ... okUsing with Single ParametersIf a test function only accepts one parameter and the value is not iterable,
then it is possible to supply a list of values without wrapping each one in a
tuple:@parameterized([1,2,3])deftest_greater_than_zero(value):assertvalue>0Note, however, that if the single parameterisiterable (such as a list or
tuple), then itmustbe wrapped in a tuple, list, or theparam(...)helper:@parameterized([([1,2,3],),([3,3],),([6],),])deftest_sums_to_6(numbers):assertsum(numbers)==6(note, also, that Python requires single element tuples to be defined with a
trailing comma:(foo, ))Using with@mock.patchparameterizedcan be used withmock.patch, but the argument ordering
can be confusing. The@mock.patch(...)decorator must comebelowthe@parameterized(...), and the mocked parameters must comelast:@mock.patch("os.getpid")classTestOS(object):@parameterized(...)@mock.patch("os.fdopen")@mock.patch("os.umask")deftest_method(self,param1,param2,...,mock_umask,mock_fdopen,mock_getpid):...Note: the same holds true when using@parameterized.expand.Migrating fromnose-parameterizedtoparameterizedTo migrate a codebase fromnose-parameterizedtoparameterized:Update your requirements file, replacingnose-parameterizedwithparameterized.Replace all references tonose_parameterizedwithparameterized:$ perl -pi -e 's/nose_parameterized/parameterized/g' your-codebase/You’re done!FAQWhat happened to Python 2.X, 3.5, and 3.6 support?As of version 0.9.0,parameterizedno longer supports Python 2.X, 3.5,
or 3.6.  Previous versions ofparameterized- 0.8.1 being the latest -
will continue to work, but will not receive any new features or bug fixes.What do you mean when you say “nose is best supported”?There are small caveates withpy.testandunittest:py.testdoes not show the parameter values (ex, it will showtest_add[0]instead oftest_add[1, 2, 3]), andunittest/unittest2do not
support test generators so@parameterized.expandmust be used.Why not use@pytest.mark.parametrize?Because spelling is difficult. Also,parameterizeddoesn’t require you
to repeat argument names, and (usingparam) it supports optional
keyword arguments.Why do I get anAttributeError: 'function' object has no attribute 'expand'with@parameterized.expand?You’ve likely installed theparametrized(note the missinge)
package. Useparameterized(with thee) instead and you’ll be all
set.What happened tonose-parameterized?Originally only nose was supported. But now everything is supported, and it
only made sense to change the name!