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

Posted

技术标签:

【中文标题】如何在 Python 中生成动态(参数化)单元测试?【英文标题】:How do you generate dynamic (parameterized) unit tests in Python? 【发布时间】:2010-09-07 04:03:13 【问题描述】:

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

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()

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

【问题讨论】:

Python unittest: Generate multiple tests programmatically?的可能重复 一个可以提供答案的好链接:eli.thegreenplace.net/2014/04/02/… 【参考方案1】:

这称为“参数化”。

有多种工具支持这种方法。例如:

pytest's decorator 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)

这将生成测试:

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'

由于历史原因,我将在 2008 年左右保留原始答案):

我使用这样的东西:

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()

【讨论】:

实际上,bignose,这段代码确实会为每个测试生成一个不同的名称(否则它实际上不会工作)。在给出的示例中,执行的测试将分别命名为“test_foo”、“test_bar”和“test_lee”。因此,只要您生成合理的名称,您提到的好处(而且是一个很大的好处)就会保留下来。 正如@codeape 给出的答案所述,nose 处理了这个问题。但是,nose 似乎无法处理 Unicode;因此对我来说这是一个更可取的解决方案。 +1 所以请注意,在 duplicate 问题中给出了更正确的答案:***.com/a/2799009/322020 - 您必须使用 .__name__ = 来启用 .exact_method i> 测试 为什么修改类的代码会出现在if __name__ == '__main__'条件中?当然,它应该在导入时运行(请记住,即使从几个不同的地方导入 python 模块也只会导入一次) 我认为这不是一个好的解决方案。单元测试的代码不应依赖于它被调用的方式。 TestCase 应该可以在鼻子或 pytest 或不同的测试环境中使用。【参考方案2】:

使用unittest(自 3.4 起)

从 Python 3.4 开始,标准库 unittest 包具有 subTest 上下文管理器。

查看文档:

26.4.7. Distinguishing test iterations using subtests 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)

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

with self.subTest(msg="Checking if p1 equals p2", p1=p1, p2=p2):

使用nose

nose 测试框架supports this。

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

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

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)

【讨论】:

但是请注意,'setup()' 不会知道哪些变量被用作 yield 的参数。实际上 setup() 不知道正在运行什么测试,或者在 test_generator() 中设置了变量。这使得 setup() 中的完整性检查变得复杂,这也是一些人更喜欢 py.test 的原因之一。 这行得通!但是您不能使用 unittest.TestCase 及其 assert* 方法系列。 有没有办法用 pytest 运行 unittest 版本,这样它就可以运行所有的情况,而不是在第一个失败的参数上停止? 正如@kakk11 所提到的,这个答案(以及一般的 subTest)不适用于 pytest。这是一个已知的问题。有一个积极开发的插件可以使这项工作:github.com/pytest-dev/pytest-subtests 有两个问题:1)第一个失败的子测试导致后续子测试没有运行 2)没有调用setUp()tearDown()进行子测试【参考方案3】:

这可以使用元类优雅地解决:

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()

【讨论】:

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

从 Python 3.4 开始,子测试已被引入到 unittest 用于此目的。有关详细信息,请参阅the documentation。 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)

测试运行的输出是:

======================================================================
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

这也是unittest2 的一部分,因此它适用于早期版本的 Python。

【讨论】:

使用python 3.4及更高版本的最佳解决方案。 使用 unittest2,这也适用于 Python 2.7。 这种方法与单独测试之间的一个主要区别是测试状态不会每次都重置。 (也就是说,setUp()tearDown() 不会在子测试之间运行。) @KevinChristopherHenry 是的,但理论上可以从子测试中手动调用self.setUp()。至于tearDown,最后自动调用就够了。 我认为当与上面的元类方法结合使用时,这可能会很强大。【参考方案5】:

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

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

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

【讨论】:

上面的代码失败:TypeError: __init__() 最多接受2个参数(给定4个) 为构造函数的额外参数添加了 null 默认值。 我更喜欢 @mojo's answer 中的鼻子参数化代码,但对于我的客户来说,避免额外的依赖太有用了,所以我将为他们使用它。 这个解决方案是我在这个页面上最喜欢的。 Nose,在当前最佳答案中建议,其分支Nose2 仅用于维护,后者建议用户改为尝试pytest。真是一团糟——我会坚持这样的原生方法! 奖励:能够为参数中传递的输出重新定义 shortDescription 方法【参考方案6】:

可以使用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

然后使用命令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 ====================

很简单!此外pytest 还具有更多功能,例如fixturesmarkassert 等。

【讨论】:

我正在寻找一个简单、直接的示例,如何使用 py.test 参数化测试用例。非常感谢! @timgeb 很高兴为您提供帮助。查看py.test 标签,了解更多示例。此外,我建议使用hamcrest 在您的断言中添加一些糖,使用人类可读的mutchers,可以通过您自己的方式修改、组合或创建。此外,我们还有allure-python,为py.test 生成漂亮的报告 谢谢。我刚开始从 unittest 转移到 py.test。我曾经有 TestCase 基类,它们能够动态创建具有不同参数的子类,它们将存储为类变量......这有点笨拙。 @timgeb 是的,你是对的。 py.test 的最杀手级功能是yield_fixtures。它可以进行setup,将一些有用的数据返回到测试中,并在测试结束后进行teardown。夹具也可以是parametirized。【参考方案7】:

使用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))

这个库可以用pip 安装。它不需要nose,并且与标准库unittest 模块配合使用非常好。

【讨论】:

【参考方案8】:

您将从尝试TestScenarios 库中受益。

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

【讨论】:

【参考方案9】:

还有Hypothesis,它添加了模糊或基于属性的测试。

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

【讨论】:

我无法在 unittest 类中使用@given() 宏。 看看这个:hypothesis.readthedocs.io/en/master/…【参考方案10】:

您可以使用nose-ittr 插件 (pip install nose-ittr)。

与现有测试集成非常容易,并且只需进行最少的更改(如果有的话)。它还支持 nose 多处理插件。

请注意,您还可以为每个测试自定义 setup 函数。

@ittr(number=[1, 2, 3, 4])
def test_even(self):
    assert_equal(self.number % 2, 0)

也可以传递nosetest 参数,就像他们的内置插件attrib 一样。这样您就可以只运行具有特定参数的特定测试:

nosetest -a number=2

【讨论】:

我喜欢这种方法,尤其是它支持的每个方法级别。【参考方案11】:

我使用元类和装饰器来生成测试。你可以查看我的实现python_wrap_cases。该库不需要任何测试框架。

你的例子:

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case("foo", "a", "a")
    @wrap_case("bar", "a", "b")
    @wrap_case("lee", "b", "b")
    def testsample(self, name, a, b):
        print "test", name
        self.assertEqual(a, b)

控制台输出:

testsample_u'bar'_u'a'_u'b' (tests.example.test_***.TestSequence) ... test bar
FAIL
testsample_u'foo'_u'a'_u'a' (tests.example.test_***.TestSequence) ... test foo
ok
testsample_u'lee'_u'b'_u'b' (tests.example.test_***.TestSequence) ... test lee
ok

你也可以使用 generators。例如,此代码生成带有参数a__listb__list 的所有可能的测试组合

import unittest
from python_wrap_cases import wrap_case


@wrap_case
class TestSequence(unittest.TestCase):

    @wrap_case(a__list=["a", "b"], b__list=["a", "b"])
    def testsample(self, a, b):
        self.assertEqual(a, b)

控制台输出:

testsample_a(u'a')_b(u'a') (tests.example.test_***.TestSequence) ... ok
testsample_a(u'a')_b(u'b') (tests.example.test_***.TestSequence) ... FAIL
testsample_a(u'b')_b(u'a') (tests.example.test_***.TestSequence) ... FAIL
testsample_a(u'b')_b(u'b') (tests.example.test_***.TestSequence) ... ok

【讨论】:

【参考方案12】:

这实际上与上一个答案中提到的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

示例用法:

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

【讨论】:

【参考方案13】:

前几天我在查看radon (example usage on the GitHub repository) 的源代码时遇到了ParamUnittest。它应该与扩展 TestCase 的其他框架(如 Nose)一起使用。

这是一个例子:

import unittest
import paramunittest


@paramunittest.parametrized(
    ('1', '2'),
    #(4, 3),    <---- Uncomment to have a failing test
    ('2', '3'),
    (('4', ), 'b': '5'),
    ((), 'a': 5, 'b': 6),
    'a': 5, 'b': 6,
)
class TestBar(TestCase):
    def setParameters(self, a, b):
        self.a = a
        self.b = b

    def testLess(self):
        self.assertLess(self.a, self.b)

【讨论】:

【参考方案14】:
import unittest

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

def add_test_methods(test_class):
    # The first element of list is variable "a", then variable "b", then name of test case that will be used as suffix.
    test_list = [[2,3, 'one'], [5,5, 'two'], [0,0, 'three']]
    for case in test_list:
        test = generator(test_class, case[0], case[1])
        setattr(test_class, "test_%s" % case[2], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print 'Setup'
        pass

    def tearDown(self):
        print 'TearDown'
        pass

_add_test_methods(TestAuto)  # It's better to start with underscore so it is not detected as a test itself

if __name__ == '__main__':
    unittest.main(verbosity=1)

结果:

>>>
Setup
FTearDown
Setup
TearDown
.Setup
TearDown
.
======================================================================
FAIL: test_one (__main__.TestAuto)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "D:/inchowar/Desktop/PyTrash/test_auto_3.py", line 5, in test
    self.assertEqual(a, b)
AssertionError: 2 != 3

----------------------------------------------------------------------
Ran 3 tests in 0.019s

FAILED (failures=1)

【讨论】:

def add_test_methods 函数存在小问题。我想应该是def _add_test_methods @Raychaser...你是对的..我修复了它,但没有在此处更新....感谢您的关注。【参考方案15】:

只需使用元类,如此处所示;

class DocTestMeta(type):
    """
    Test functions are generated in metaclass due to the way some
    test loaders work. For example, setupClass() won't get called
    unless there are other existing test methods, and will also
    prevent unit test loader logic being called before the test
    methods have been defined.
    """
    def __init__(self, name, bases, attrs):
        super(DocTestMeta, self).__init__(name, bases, attrs)

    def __new__(cls, name, bases, attrs):
        def func(self):
            """Inner test method goes here"""
            self.assertTrue(1)

        func.__name__ = 'test_sample'
        attrs[func.__name__] = func
        return super(DocTestMeta, cls).__new__(cls, name, bases, attrs)

class ExampleTestCase(TestCase):
    """Our example test case, with no methods defined"""
    __metaclass__ = DocTestMeta

输出:

test_sample (ExampleTestCase) ... OK

【讨论】:

【参考方案16】:

我在使用一种非常特殊的参数化测试风格时遇到了麻烦。我们所有的 Selenium 测试都可以在本地运行,但它们也应该能够在 SauceLabs 上的多个平台上远程运行。基本上,我想获取大量已经编写好的测试用例,并用尽可能少的代码更改来参数化它们。此外,我需要能够将参数传递给 setUp 方法,这是我在其他地方没有看到的任何解决方案。

这是我想出的:

import inspect
import types

test_platforms = [
    'browserName': "internet explorer", 'platform': "Windows 7", 'version': "10.0",
    'browserName': "internet explorer", 'platform': "Windows 7", 'version': "11.0",
    'browserName': "firefox", 'platform': "Linux", 'version': "43.0",
]


def sauce_labs():
    def wrapper(cls):
        return test_on_platforms(cls)
    return wrapper


def test_on_platforms(base_class):
    for name, function in inspect.getmembers(base_class, inspect.isfunction):
        if name.startswith('test_'):
            for platform in test_platforms:
                new_name = '_'.join(list([name, ''.join(platform['browserName'].title().split()), platform['version']]))
                new_function = types.FunctionType(function.__code__, function.__globals__, new_name,
                                                  function.__defaults__, function.__closure__)
                setattr(new_function, 'platform', platform)
                setattr(base_class, new_name, new_function)
            delattr(base_class, name)

    return base_class

有了这个,我所要做的就是为每个常规的旧TestCase添加一个简单的装饰器@sauce_labs(),现在在运行它们时,它们被包装并重写,因此所有的测试方法都被参数化并重命名. LoginTests.test_login(self) 作为 LoginTests.test_login_internet_explorer_10.0(self)、LoginTests.test_login_internet_explorer_11.0(self) 和 LoginTests.test_login_firefox_43.0(self) 运行,每个都有参数 self.platform 来决定什么浏览器/运行的平台,甚至在 LoginTests.setUp 中,这对我的任务至关重要,因为这是与 SauceLabs 的连接被初始化的地方。

无论如何,我希望这对希望对其测试进行类似“全局”参数化的人有所帮助!

【讨论】:

【参考方案17】:

您可以使用TestSuite 和自定义TestCase 类。

import unittest

class CustomTest(unittest.TestCase):
    def __init__(self, name, a, b):
        super().__init__()
        self.name = name
        self.a = a
        self.b = b

    def runTest(self):
        print("test", self.name)
        self.assertEqual(self.a, self.b)

if __name__ == '__main__':
    suite = unittest.TestSuite()
    suite.addTest(CustomTest("Foo", 1337, 1337))
    suite.addTest(CustomTest("Bar", 0xDEAD, 0xC0DE))
    unittest.TextTestRunner().run(suite)

【讨论】:

当 TestSuite 工作时,参数不会传递给 __init__ 函数。【参考方案18】:

我发现这对我的目的很有效,尤其是当我需要生成对数据集合进行略微不同处理的测试时。

import unittest

def rename(newName):
    def renamingFunc(func):
        func.__name__ == newName
        return func
    return renamingFunc

class TestGenerator(unittest.TestCase):

    TEST_DATA = 

    @classmethod
    def generateTests(cls):
        for dataName, dataValue in TestGenerator.TEST_DATA:
            for func in cls.getTests(dataName, dataValue):
                setattr(cls, "test_:s_:s".format(func.__name__, dataName), func)

    @classmethod
    def getTests(cls):
        raise(NotImplementedError("This must be implemented"))

class TestCluster(TestGenerator):

    TEST_CASES = []

    @staticmethod
    def getTests(dataName, dataValue):

        def makeTest(case):

            @rename(":s".format(case["name"]))
            def test(self):
                # Do things with self, case, data
                pass

            return test

        return [makeTest(c) for c in TestCluster.TEST_CASES]

TestCluster.generateTests()

TestGenerator 类可用于生成不同的测试用例集,例如 TestCluster

TestCluster 可以被认为是TestGenerator 接口的实现。

【讨论】:

【参考方案19】:

此解决方案适用于 Python 2 和 Python 3 的 unittestnose

#!/usr/bin/env python
import unittest

def make_function(description, a, b):
    def ghost(self):
        self.assertEqual(a, b, description)
    print(description)
    ghost.__name__ = 'test_0'.format(description)
    return ghost


class TestsContainer(unittest.TestCase):
    pass

testsmap = 
    'foo': [1, 1],
    'bar': [1, 2],
    'baz': [5, 5]

def generator():
    for name, params in testsmap.iteritems():
        test_func = make_function(name, params[0], params[1])
        setattr(TestsContainer, 'test_0'.format(name), test_func)

generator()

if __name__ == '__main__':
    unittest.main()

【讨论】:

感谢@guillaume-jacquenot 的升级版 【参考方案20】:

元编程很有趣,但它可能会妨碍您。这里的大多数解决方案都很难:

选择性地启动测试 返回给定测试名称的代码

所以,我的第一个建议是遵循简单/显式路径(适用于任何测试运行器):

import unittest

class TestSequence(unittest.TestCase):

    def _test_complex_property(self, a, b):
        self.assertEqual(a,b)

    def test_foo(self):
        self._test_complex_property("a", "a")
    def test_bar(self):
        self._test_complex_property("a", "b")
    def test_lee(self):
        self._test_complex_property("b", "b")

if __name__ == '__main__':
    unittest.main()

既然我们不应该重复自己,我的第二个建议基于Javier's answer:接受基于属性的测试。假设库:

“在测试用例生成方面比我们人类更无情”

将提供简单的计数示例

适用于任何测试运行器

具有更多有趣的功能(统计、额外的测试输出……)

类TestSequence(unittest.TestCase):

  @given(st.text(), st.text())
  def test_complex_property(self, a, b):
      self.assertEqual(a,b)

要测试您的具体示例,只需添加:

    @example("a", "a")
    @example("a", "b")
    @example("b", "b")

要仅运行一个特定示例,您可以注释掉其他示例(前提是先运行示例)。您可能想使用@given(st.nothing())。另一种选择是将整个块替换为:

    @given(st.just("a"), st.just("b"))

好的,您没有不同的测试名称。但也许你只需要:

被测属性的描述性名称。 哪个输入导致失败(伪造示例)。

Funnier example

【讨论】:

【参考方案21】:

我无法为setUpClass 完成这些工作。

这是Javier's answer 的一个版本,它允许setUpClass 访问动态分配的属性。

import unittest


class GeneralTestCase(unittest.TestCase):
    @classmethod
    def setUpClass(cls):
        print ''
        print cls.p1
        print cls.p2

    def runTest1(self):
        self.assertTrue((self.p2 - self.p1) == 1)

    def runTest2(self):
        self.assertFalse((self.p2 - self.p1) == 2)


def load_tests(loader, tests, pattern):
    test_cases = unittest.TestSuite()
    for p1, p2 in [(1, 2), (3, 4)]:
        clsname = 'TestCase__'.format(p1, p2)
        dct = 
            'p1': p1,
            'p2': p2,
        
        cls = type(clsname, (GeneralTestCase,), dct)
        test_cases.addTest(cls('runTest1'))
        test_cases.addTest(cls('runTest2'))
    return test_cases

输出

1
2
..
3
4
..
----------------------------------------------------------------------
Ran 4 tests in 0.000s

OK

【讨论】:

【参考方案22】:

基于元类的答案在 Python 3 中仍然有效,但必须使用 metaclass 参数而不是 __metaclass__ 属性,如下所示:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

【讨论】:

【参考方案23】:
import unittest

def generator(test_class, a, b,c,d,name):
    def test(self):
        print('Testexecution=',name)
        print('a=',a)
        print('b=',b)
        print('c=',c)
        print('d=',d)

    return test

def add_test_methods(test_class):
    test_list = [[3,3,5,6, 'one'], [5,5,8,9, 'two'], [0,0,5,6, 'three'],[0,0,2,3,'Four']]
    for case in test_list:
        print('case=',case[0], case[1],case[2],case[3],case[4])
        test = generator(test_class, case[0], case[1],case[2],case[3],case[4])
        setattr(test_class, "test_%s" % case[4], test)


class TestAuto(unittest.TestCase):
    def setUp(self):
        print ('Setup')
        pass

    def tearDown(self):
        print ('TearDown')
        pass

add_test_methods(TestAuto)

if __name__ == '__main__':
    unittest.main(verbosity=1)

【讨论】:

您好像丢失了那里的格式。就目前而言,真的很难阅读 解释一下。【参考方案24】:

以下是我的解决方案。我发现这在以下情况下很有用:

    应该适用于 unittest.Testcase 和 unittest discover

    针对不同的参数设置运行一组测试。

    非常简单,不依赖其他包

     import unittest
    
     class BaseClass(unittest.TestCase):
         def setUp(self):
             self.param = 2
             self.base = 2
    
         def test_me(self):
             self.assertGreaterEqual(5, self.param+self.base)
    
         def test_me_too(self):
             self.assertLessEqual(3, self.param+self.base)
    
    
      class Child_One(BaseClass):
         def setUp(self):
             BaseClass.setUp(self)
             self.param = 4
    
    
      class Child_Two(BaseClass):
         def setUp(self):
             BaseClass.setUp(self)
             self.param = 1
    

【讨论】:

这没有回答问题,即动态生成测试。【参考方案25】:

除了使用 setattr,我们还可以在 Python 3.2 及更高版本中使用 load_tests

class Test(unittest.TestCase):
    pass

def _test(self, file_name):
    open(file_name, 'r') as f:
        self.assertEqual('test result',f.read())

def _generate_test(file_name):
    def test(self):
        _test(self, file_name)
    return test

def _generate_tests():
    for file in files:
        file_name = os.path.splitext(os.path.basename(file))[0]
        setattr(Test, 'test_%s' % file_name, _generate_test(file))

test_cases = (Test,)

def load_tests(loader, tests, pattern):
    _generate_tests()
    suite = TestSuite()
    for test_class in test_cases:
        tests = loader.loadTestsFromTestCase(test_class)
        suite.addTests(tests)
    return suite

if __name__ == '__main__':
    _generate_tests()
    unittest.main()

【讨论】:

链接已损坏(DNS?):“嗯。我们无法找到该站点。我们无法连接到 blog.livreuro.com 上的服务器。”我>

以上是关于如何在 Python 中生成动态(参数化)单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

如何不在 Python、Django 中生成两次相同的动态图像?

nose-parameterized是Python单元测试框架实现参数化的扩展

单元测试框架参数化测试报告

如何在我的颤振测试中生成未测试文件的测试覆盖率?

Python单元测试框架之unittest参数化(paramunittest)

如何使用Visual Studio 2010在数据库中生成随机测试数据