你如何在python中生成动态(参数化)单元测试?

Pet*_*ann 211 python unit-testing parameterized-unit-test

我有一些测试数据,想为每个项目创建一个单元测试.我的第一个想法是这样做:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequence(unittest.TestCase):
    def testsample(self):
        for name, a,b in l:
            print "test", name
            self.assertEqual(a,b)

if __name__ == '__main__':
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

这样做的缺点是它在一次测试中处理所有数据.我想在运行中为每个项目生成一个测试.有什么建议?

Dmi*_*hin 153

我使用这样的东西:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequense(unittest.TestCase):
    pass

def test_generator(a, b):
    def test(self):
        self.assertEqual(a,b)
    return test

if __name__ == '__main__':
    for t in l:
        test_name = 'test_%s' % t[0]
        test = test_generator(t[1], t[2])
        setattr(TestSequense, test_name, test)
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

parameterized包可用于自动执行此过程:

from parameterized import parameterized

class TestSequence(unittest.TestCase):
    @parameterized.expand([
        ["foo", "a", "a",],
        ["bar", "a", "b"],
        ["lee", "b", "b"],
    ])
    def test_sequence(self, name, a, b):
        self.assertEqual(a,b)
Run Code Online (Sandbox Code Playgroud)

哪个会生成测试:

test_sequence_0_foo (__main__.TestSequence) ... ok
test_sequence_1_bar (__main__.TestSequence) ... FAIL
test_sequence_2_lee (__main__.TestSequence) ... ok

======================================================================
FAIL: test_sequence_1_bar (__main__.TestSequence)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/local/lib/python2.7/site-packages/parameterized/parameterized.py", line 233, in <lambda>
    standalone_func = lambda *a: func(*(a + p.args), **p.kwargs)
  File "x.py", line 12, in test_sequence
    self.assertEqual(a,b)
AssertionError: 'a' != 'b'
Run Code Online (Sandbox Code Playgroud)

  • 实际上,bignose,这个代码为每个测试生成一个不同的名称(否则它实际上不起作用).在给出的示例中,执行的测试将分别命名为"test_foo","test_bar"和"test_lee".因此,只要您生成合理的名称,就会保留您提到的好处(并且它是一个很大的好处). (23认同)
  • 为什么修改类的代码出现在`if __name__ =='__ main __'`条件中?当然它应该超出这个以在导入时运行(记住即使从几个不同的地方导入python模块也只导入一次) (7认同)
  • 请注意,在*duplicate*问题中给出了更正确的答案:http://stackoverflow.com/a/2799009/322020 - 您已经使用`.__ name__ =`来启用*`.exact_method`*测试 (5认同)
  • 我不认为这是一个很好的解决方案.unittest的代码不应该取决于它被调用的方式.TestCase应该可用于鼻子或pytest或不同的测试环境. (4认同)
  • 正如 @codeape 给出的答案所述,nose 可以处理这个问题。然而,nose 似乎不能处理 Unicode;因此对我来说这是一个更好的解决方案。+1 (2认同)
  • 我的印象是,这是 10 年前的一个很好的答案。但它得到了如此多的支持,因为它是最古老的答案,而不是最好的答案。如今,要走的路可能是 py.test 的参数化装饰器。(元类很丑陋,并且不能与 py.test 一起使用;许多一次性软件包不再开发或支持。) (2认同)

cod*_*ape 123

使用unittest(自3.4起)

从Python 3.4开始,标准库unittest包就有了subTest上下文管理器.

查看文档:

例:

from unittest import TestCase

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

class TestDemonstrateSubtest(TestCase):
    def test_works_as_expected(self):
        for p1, p2 in param_list:
            with self.subTest():
                self.assertEqual(p1, p2)
Run Code Online (Sandbox Code Playgroud)

您还可以指定自定义消息和参数值subTest():

with self.subTest(msg="Checking if p1 equals p2", p1=p1, p2=p2):
Run Code Online (Sandbox Code Playgroud)

用鼻子

测试框架支持此.

示例(下面的代码是包含测试的文件的全部内容):

param_list = [('a', 'a'), ('a', 'b'), ('b', 'b')]

def test_generator():
    for params in param_list:
        yield check_em, params[0], params[1]

def check_em(a, b):
    assert a == b
Run Code Online (Sandbox Code Playgroud)

nosetests命令的输出:

> nosetests -v
testgen.test_generator('a', 'a') ... ok
testgen.test_generator('a', 'b') ... FAIL
testgen.test_generator('b', 'b') ... ok

======================================================================
FAIL: testgen.test_generator('a', 'b')
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/usr/lib/python2.5/site-packages/nose-0.10.1-py2.5.egg/nose/case.py", line 203, in runTest
    self.test(*self.arg)
  File "testgen.py", line 7, in check_em
    assert a == b
AssertionError

----------------------------------------------------------------------
Ran 3 tests in 0.006s

FAILED (failures=1)
Run Code Online (Sandbox Code Playgroud)

  • 有两个问题: 1) 第一个失败的子测试导致后续子测试无法运行 2) 子测试不会调用 `setUp()` 和 `tearDown()` (4认同)
  • 这是一种动态生成测试用例的非常简洁的方法. (3认同)
  • 有没有办法使用 pytest 运行单元测试版本,以便它可以运行所有情况而不是在第一个失败的参数上停止? (2认同)
  • 正如@kakk11 所提到的,这个答案(以及一般的 subTest)不适用于 pytest。这是一个已知的问题。有一个积极开发的插件来完成这项工作:https://github.com/pytest-dev/pytest-subtests (2认同)

Guy*_*Guy 69

这可以使用Metaclasses优雅地解决:

import unittest

l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]]

class TestSequenceMeta(type):
    def __new__(mcs, name, bases, dict):

        def gen_test(a, b):
            def test(self):
                self.assertEqual(a, b)
            return test

        for tname, a, b in l:
            test_name = "test_%s" % tname
            dict[test_name] = gen_test(a,b)
        return type.__new__(mcs, name, bases, dict)

class TestSequence(unittest.TestCase):
    __metaclass__ = TestSequenceMeta

if __name__ == '__main__':
    unittest.main()
Run Code Online (Sandbox Code Playgroud)

  • 注意:在python 3中,将其更改为:`class TestSequence(unittest.TestCase,metaclass = TestSequenceMeta):[...]` (7认同)
  • 你能用`dct`代替`dict`吗?使用关键字作为变量名容易混淆且容易出错。 (6认同)
  • 这对我使用 Selenium 非常有用。注意,在 TestSequence 类中,您可以定义“静态”方法,如 setUp(self)、is_element_present(self, how, what), ... tearDown(self)。将它们放在“__metaclass__ = TestSequenceMeta”语句之后似乎有效。 (2认同)
  • 该解决方案优于作为已接受的恕我直言选择的解决方案. (2认同)
  • @petroslamb 元类中的 `__new__` 方法在定义类本身时被调用,而不是在创建第一个实例时调用。我认为这种动态创建测试方法的方法与 `unittest` 用来确定类中有多少测试的内省更兼容(即它可以在创建该类的实例之前编译测试列表)。 (2认同)

Ber*_*ard 44

从Python 3.4开始,为了这个目的,已经将单测试引入了单元测试.有关详细信息,请参阅文档 TestCase.subTest是一个上下文管理器,它允许在测试中隔离断言,以便使用参数信息报告失败但不会停止测试执行.以下是文档中的示例:

class NumbersTest(unittest.TestCase):

def test_even(self):
    """
    Test that numbers between 0 and 5 are all even.
    """
    for i in range(0, 6):
        with self.subTest(i=i):
            self.assertEqual(i % 2, 0)
Run Code Online (Sandbox Code Playgroud)

测试运行的输出将是:

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=1)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=3)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0

======================================================================
FAIL: test_even (__main__.NumbersTest) (i=5)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "subtests.py", line 32, in test_even
    self.assertEqual(i % 2, 0)
AssertionError: 1 != 0
Run Code Online (Sandbox Code Playgroud)

这也是unittest2的一部分,因此可用于早期版本的Python.

  • 这种方法与单独测试之间的一个主要区别是每次都不会重置测试状态.(也就是说,子集测试之间不会运行`setUp()`和`tearDown()`.) (9认同)
  • 使用unittest2,它也适用于Python 2.7. (4认同)
  • 如果您使用 python 3.4 及更高版本,则是最佳解决方案。 (2认同)
  • @KevinChristopherHenry 是的,但理论上可以从子测试中手动调用`self.setUp()`。至于`tearDown`,在最后自动调用它就足够了。 (2认同)

Jav*_*ier 36

load_tests是2.7中引入的一种鲜为人知的机制,用于动态创建TestSuite.有了它,您可以轻松创建参数化测试.

例如:

import unittest

class GeneralTestCase(unittest.TestCase):
    def __init__(self, methodName, param1=None, param2=None):
        super(GeneralTestCase, self).__init__(methodName)

        self.param1 = param1
        self.param2 = param2

    def runTest(self):
        pass  # Test that depends on param 1 and 2.


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        test_cases.addTest(GeneralTestCase('runTest', p1, p2))
    return test_cases
Run Code Online (Sandbox Code Playgroud)

该代码将运行load_tests返回的TestSuite中的所有TestCase.发现机制不会自动运行其他测试.

或者,您也可以使用此票证中显示的继承:http://bugs.python.org/msg151444

  • 为构造函数额外参数添加了null默认值. (2认同)

Ser*_*kiy 29

它可以通过使用pytest来完成.只需test_me.py用内容编写文件:

import pytest

@pytest.mark.parametrize('name, left, right', [['foo', 'a', 'a'],
                                               ['bar', 'a', 'b'],
                                               ['baz', 'b', 'b']])
def test_me(name, left, right):
    assert left == right, name
Run Code Online (Sandbox Code Playgroud)

并使用命令运行测试py.test --tb=short test_me.py.然后输出将如下所示:

=========================== test session starts ============================
platform darwin -- Python 2.7.6 -- py-1.4.23 -- pytest-2.6.1
collected 3 items

test_me.py .F.

================================= FAILURES =================================
_____________________________ test_me[bar-a-b] _____________________________
test_me.py:8: in test_me
    assert left == right, name
E   AssertionError: bar
==================== 1 failed, 2 passed in 0.01 seconds ====================
Run Code Online (Sandbox Code Playgroud)

很简单!此外pytest具有更多的功能,如fixtures,mark,assert,等...


Myk*_*nko 9

使用ddt库.它为测试方法添加了简单的装饰器:

import unittest
from ddt import ddt, data
from mycode import larger_than_two

@ddt
class FooTestCase(unittest.TestCase):

    @data(3, 4, 12, 23)
    def test_larger_than_two(self, value):
        self.assertTrue(larger_than_two(value))

    @data(1, -3, 2, 0)
    def test_not_larger_than_two(self, value):
        self.assertFalse(larger_than_two(value))
Run Code Online (Sandbox Code Playgroud)

可以安装此库pip.它不需要nose,并且与标准库unittest模块一起使用非常好.


big*_*ose 6

您将从试用TestScenarios库中受益.

testscenarios为python unittest样式测试提供了干净的依赖注入.这可用于接口测试(通过单个测试套件测试许多实现)或经典依赖注入(在测试代码本身外部提供依赖性测试,允许在不同情况下轻松测试).


Eri*_*eau 6

parameterized这实际上与之前的答案中提到的相同,但特定于unittest

def sub_test(param_list):
    """Decorates a test case to run it as a set of subtests."""

    def decorator(f):

        @functools.wraps(f)
        def wrapped(self):
            for param in param_list:
                with self.subTest(**param):
                    f(self, **param)

        return wrapped

    return decorator
Run Code Online (Sandbox Code Playgroud)

用法示例:

class TestStuff(unittest.TestCase):
    @sub_test([
        dict(arg1='a', arg2='b'),
        dict(arg1='x', arg2='y'),
    ])
    def test_stuff(self, arg1, arg2):
        ...
Run Code Online (Sandbox Code Playgroud)


Jav*_*ier 5

还有假设,它增加了模糊或基于属性的测试:https://pypi.python.org/pypi/hypothesis

这是一种非常强大的测试方法.