【问题标题】:Py.test: parametrize test cases from classesPy.test:对类中的测试用例进行参数化
【发布时间】:2016-02-22 19:08:58
【问题描述】:

我目前正在关注这个 py.test 示例,当我不使用类时它可以解决,但是当我将测试用例引入类时我失败了。

我设法写的最小的情况如下:

import unittest

import pytest

class FixtureTestCase(unittest.TestCase):

    @pytest.mark.parametrize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
    ])
    def test_1(self, a, b):
        self.assertEqual(a, b)

不幸的是当我执行时

  py.test  test_suite.py

我收到错误消息:

  TypeError: test_1() takes exactly 3 arguments (1 given)

如何才能生成一组 test_1 测试?

【问题讨论】:

  • 你的类需要是TestCase的子类吗?
  • 目前,这不是强制性的,我需要的是参数化定义到一个类中的测试
  • 对于TestCase 方法的参数化测试,这个旧问题的答案似乎仍然有效:stackoverflow.com/questions/18182251/… 简而言之:混合这两个概念是行不通的。

标签: python unit-testing automated-tests pytest


【解决方案1】:

如果你从unittest.TestCase 继承,你的测试方法不能有额外的参数。如果您只是从object 继承子类,它会起作用(尽管您必须使用常规的assert 语句而不是TestCase.assertEqual 方法。

import unittest

import pytest

class TestCase(object):

    @pytest.mark.parametrize("test_input,expected", [
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
    ])
    def test_1(self, a, b):
        assert eval(a) == b

不过,在这一点上,它有点引出了一个问题,为什么您使用类而不是仅仅定义函数,因为测试基本上是相同的,但需要更少的整体样板和代码。

【讨论】:

  • 这个提议可行,但 pytest 没有识别任何测试。它打印出图例“收集了 0 个项目”。有什么建议吗?
  • 对不起,我还想指出类名需要以Test 开头,pytest 才能接它。
  • 我刚刚接受了答案,但是我想知道当类继承自 unittest.TestCase 时是否还有一个选项?
  • 您将无法使用parametrize。 @ThomasLotze 的 docs 链接对此进行了解释。它们为绕过限制提供了一些可能性,在您的情况下,这可能涉及在类上设置测试值,然后在测试函数中循环它以运行具有多个测试值的测试。
  • 我想知道这是有原因的,以防万一我需要重新实现一些测试类。
【解决方案2】:

我不知道 5 年前是否是这种情况,但这些天你可以使用参数化 (https://pypi.org/project/parameterized/) 和 pytest 来装饰测试类上的测试方法,是的,包括 unittest.TestCase,而不必求助到鼻子。例如:

from unittest import TestCase
from parameterized import parameterized

class SomeTestCase(TestCase):

    @parameterized.expand([
        (1, 2),
        ('a', 'b')
    ])
    def test_something(self, param1, param2):
        ...

唯一的问题,但您最好记住这一点,装饰器将为每个列出的输入参数生成新的测试方法,因此您将无法通过在命令行中指定它来直接运行原始测试方法。例如。 pytest some_test.py::SomeTestCase::test_something 将不再起作用(因为您的测试方法现在需要两个参数)。但是,您可以直接调用生成的方法,您可以在运行整个 TestCase 时从 pytest 错误输出或通过执行 pytest --collect-only 获取名称。

【讨论】:

  • 如果不使用 TestCase 是一个繁重的选择,这是一个很好的解决方案
  • 您可以“几乎”以这种方式运行测试:pytest some_test.py::SomeTestCase::test_something。可以在test里面加一个assert False,运行hole类可以看到parameterized生成的名字。该名称是与测试索引和参数链接的测试,因此要运行第一个测试,它将类似于:pytest some_test.py::SomeTestCase::test_something_0_1_2。然后,如果你想运行所有测试,是的,你必须运行洞类。
  • @Gonzalo 这行得通,但这比我上面描述的使用--collect-only 运行pytest 更简单或更好吗? (例如pytest --collect-only some_test.py::SomeTestCase)此外,如果您的一些参数化案例失败,您将自动获得名称,而不会强制所有案例失败。
【解决方案3】:

最后,考虑到@Brendan Abel 和 cmets 的回复,我成功地完成了我打算做的事情:

class TestCase(object):

    @parameterized.expand([
    ("negative", -1.5, -2.0),
    ("integer", 1, 1.0),
    ("large fraction", 1.6, 1),
    ])
    def test_floor(self, name, input, expected):
        assert_equal(math.floor(input), expected)


    @parameterized.expand([
    ("3+5", 8),
    ("2+4", 6),
    ("6*9", 42),
    ])
    def test_1(self, a, b):
        assert_equal(eval(a), b)

然后我可以通过nosetests 命令执行测试:

  nosetests -v --with-id class.py

【讨论】:

    【解决方案4】:

    对于那些仍然感兴趣的人,我写了一个插入式 @pytest.mark.parametrize 替换 unittest.TestCase:https://github.com/MrMrRobat/parametrize

    import unittest
    
    from parametrize import parametrize
    
    class FixtureTestCase(unittest.TestCase):
    
        @parametrize(
            "test_input,expected",
            [
                ("3+5", 8),
                ("2+4", 6),
                ("6*9", 42),
            ]
        )
        def test_1(self, test_input, expected):
            self.assertEqual(eval(test_input), expected)
    

    $ pytest test.py::FixtureTestCase::test_1

    Test session starts (platform: darwin, Python 3.9.4, pytest 6.2.4, pytest-sugar 0.9.4)
    plugins: sugar-0.9.4, cov-2.11.1, mock-3.6.0
    collecting ... 
     test.py ✓✓                                                       67% ██████▋   
    
    ―――――――――――――――――――――――― FixtureTestCase.test_1[6*9-42] ――――――――――――――――――――――――
    
    self = <test.FixtureTestCase testMethod=test_1[6*9-42]>, test_input = '6*9'
    expected = 42
    
        @parametrize(
            "test_input,expected",
            [
                ("3+5", 8),
                ("2+4", 6),
                ("6*9", 42),
            ]
        )
        def test_1(self, test_input, expected):
    >       self.assertEqual(eval(test_input), expected)
    E       AssertionError: 54 != 42
    
    test.py:16: AssertionError
    
     test.py ⨯                                                       100% ██████████
    =========================== short test summary info ============================
    FAILED test.py::FixtureTestCase::test_1[6*9-42] - AssertionError: 54 != 42
    
    Results (0.09s):
           2 passed
           1 failed
             - test.py:7 FixtureTestCase.test_1[6*9-42]
    

    【讨论】:

      猜你喜欢
      • 2018-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多