我尝试为我的单元测试添加 use pytest parametrize 。但我看到“缺少必需的位置参数”的错误。您能帮忙看看问题出在哪里吗?谢谢。
@pytest.mark.parametrize("param", ["a", "b"])
def test_pytest(self, param):
print(param)
assert False
我收到以下异常:
class Tests(unittest.TestCase):
@contextlib.contextmanager
def testPartExecutor(self, test_case, isTest=False):
old_success = self.success
self.success = True
try:
> yield
with outcome.testPartExecutor(self):
self.setUp()
if outcome.success:
outcome.expecting_failure = expecting_failure
with outcome.testPartExecutor(self, isTest=True):
> testMethod()
E TypeError: test_pytest() missing 1 required positional argument: 'param'
正如 pytest 的 documentation 对问题的评论中所引用的,pytest 的参数化不适用于
unittest.TestCase
[子]类下的方法。我可以想到几种方法来解决这个问题:
一个例子是 parameterized 包,可以按如下方式使用:
import unittest
from parameterized import parameterized
class TestClass(unittest.TestCase):
def test_ok(self):
assert True
@parameterized.expand((("a",), ("b",)))
def test_single(self, param):
print(param)
assert False
@parameterized.expand((("a", "c", "d"), ("b", "c", "d")))
def test_multiple(self, first, second, third):
self.assertEqual(second, "c")
self.assertEqual(third, "d")
self.assertTrue(first in ("a", "b"))
self.fail(f"Failing test for {first}_{second}_{third}")
pytest 将按预期处理上面的代码,得到以下摘要:
====================================== short test summary info ======================================
FAILED test.py::TestClass::test_multiple_0_a - AssertionError: Failing test for a_c_d
FAILED test.py::TestClass::test_multiple_1_b - AssertionError: Failing test for b_c_d
FAILED test.py::TestClass::test_single_0_a - assert False
FAILED test.py::TestClass::test_single_1_b - assert False
==================================== 4 failed, 1 passed in 0.06s ====================================
虽然这种扩展性很差(例如,当测试多个值或参数时),但显式声明避免引入新的第 3 方包。
使用显式方法的更麻烦的方法:
import unittest
class TestClass(unittest.TestCase):
def _test_param(self, param):
self.assertEqual(param, list(range(5)))
def test_0_to_4(self):
return self._test_param([0, 1, 2, 3, 4])
def test_sort_list(self):
return self._test_param(sorted([0, 3, 2, 1, 4]))
def test_fail(self):
return self._test_param([0, 3, 2, 1, 4])
以及使用部分方法的更精简方法:
import unittest
from functools import partialmethod
class TestClass(unittest.TestCase):
def _test_param(self, param):
self.assertEqual(param, list(range(5)))
test_0_to_4 = partialmethod(_test_param, [0, 1, 2, 3, 4])
test_sort_list = partialmethod(_test_param, sorted([0, 3, 2, 1, 4]))
test_fail = partialmethod(_test_param, [0, 3, 2, 1, 4])
虽然这些会产生不同的回溯,但 pytest 的摘要对于两者来说是相同的:
========================================= short test summary info =========================================
FAILED test.py::TestClass::test_fail - AssertionError: Lists differ: [0, 3, 2, 1, 4] != [0, 1, 2, 3, 4]
======================================= 1 failed, 2 passed in 0.02s =======================================
由于第一种方法已经可以找到一个优雅的装饰器接口,我将演示一个hacky、笨重、不可重用、但相当快速的DIY解决方案;使用“动态”测试方法注入类,将参数作为 kwargs 附加到每个方法:
import unittest
class TestClass(unittest.TestCase):
def test_ok(self):
assert True
def _test_injected_method(self, first, second):
self.assertEqual(second, "z", f"{second} is not z")
self.assertTrue(first in ("x", "y"), f"{first} is not x or y")
self.fail(f"Failing test for {first}_{second}")
for param1 in ("w", "x", "y"):
for param2 in ("z", "not_z"):
def _test(self, p1=param1, p2=param2):
return self._test_injected_method(p1, p2)
_test.__name__ = f"test_injected_method_{param1}_{param2}"
setattr(TestClass, _test.__name__, _test)
这也适用于 pytest,产生以下摘要:
========================================= short test summary info =========================================
FAILED test.py::TestClass::test_injected_method_w_not_z - AssertionError: 'not_z' != 'z'
FAILED test.py::TestClass::test_injected_method_w_z - AssertionError: False is not true : w is not x or y
FAILED test.py::TestClass::test_injected_method_x_not_z - AssertionError: 'not_z' != 'z'
FAILED test.py::TestClass::test_injected_method_x_z - AssertionError: Failing test for x_z
FAILED test.py::TestClass::test_injected_method_y_not_z - AssertionError: 'not_z' != 'z'
FAILED test.py::TestClass::test_injected_method_y_z - AssertionError: Failing test for y_z
======================================= 6 failed, 1 passed in 0.02s =======================================
恕我直言,这种方法不如第二种优雅(在我看来,这种方法不如第一种优雅),但如果引入第 3 方包不是一个选项,那么这种方法比第二种更有效地扩展。
我也有同样的问题。看起来
parametrize
并不总是在测试类中工作,但可以在独立函数上工作。
pytest.mark.parametrize
不适用于 Django 的
TestCase
类。 这是一个实现,虽然不是那么强大,但能够提供解决方法:“参数化”夹具的实现:
def parametrize(argument):
def decorator(function):
def wrapper(cls, *args, **kwargs):
for item in argument:
function(cls, *item)
return wrapper
return decorator
用途:
@parametrize(
[
(["my_value_1"]),
(["my_value_2"]),
]
)
def test_my_simple_test_class(self, parameterized_var):
# Do something with parameterized_var
@parametrize(
[
(["var_1_1"], ["var_1_2"]),
(["var_2_1"], ["var_2_2"]),
]
)
def test_my_composed_test_class(self, parameterized_var_1, parameterized_var_2):
# Do something with parameterized vars
观察:注意变量周围的括号。这是正确提取值所必需的。