pytest 参数化我缺少必需的位置参数

问题描述 投票:0回答:3

我尝试为我的单元测试添加 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'
python pytest
3个回答
4
投票

正如 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 方包不是一个选项,那么这种方法比第二种更有效地扩展。

其他方法

  • 重构测试类以不继承自
    unittest.TestCase
    是一种选择,但风险很大;无论此任务所需的技术努力如何(例如,用
    self.assert*
    语句替换所有
    assert
    调用、处理
    setUp
    tearDown
    方法等),还存在隐含的风险,除非明确指出,否则测试可能会被默默忽略使用 pytest 调用。
  • 建议其他解决方案herethere,但我相信其中大多数解决方案是解决
    nose
    而不是
    pytest
    。一些答案建议使用元类,虽然我感觉这对于这个特定问题的解决方案来说有点过分了,但如果我之前的建议不适合您的用例 - 这是值得研究的。

0
投票

我也有同样的问题。看起来

parametrize
并不总是在测试类中工作,但可以在独立函数上工作。


0
投票
如上所述,

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
观察:注意变量周围的括号。这是正确提取值所必需的。

© www.soinside.com 2019 - 2024. All rights reserved.