我有某种测试数据,并且想要为每个项目创建一个单元测试。我的第一个想法就是这样做:

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的可能重复项:以编程方式生成多个测试吗?

一个可能提供答案的良好链接:eli.thegreenplace.net/2014/04/02/…

#1 楼

这称为“参数化”。

有几种工具支持此方法。例如:


pytest的装饰器
参数化的

结果代码如下:

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'



我使用像这样的东西:

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


评论


实际上,笨拙的是,此代码确实会为每个测试生成一个不同的名称(否则它实际上将无法工作)。在给出的示例中,执行的测试将分别命名为“ test_foo”,“ test_bar”和“ test_lee”。因此,只要您生成明智的名称,您提到的好处(这是一个很大的好处)就会保留下来。

– Toji
2011-2-23在16:52

就像@codeape给出的答案一样,鼻子可以解决这个问题。但是,鼻子似乎无法处理Unicode。因此对我来说这是一个更好的解决方案。 +1

–基思·皮森(Keith Pinson)
11年8月13日在20:42

因此请注意,重复的问题给出了更正确的答案:stackoverflow.com/a/2799009/322020-您已经习惯使用.__ name__ =来启用.exact_method测试

– Nakilon
13年4月12日在10:38



为什么修改该类的代码出现在if __name__ =='__main__'条件中?当然,它应该超出此范围以在导入时运行(请记住,即使从几个不同的位置导入,python模块也只会被导入一次)

– SpoonMeiser
2013年12月21日,0:52

我认为这不是一个好的解决方案。单元测试的代码不应取决于调用它的方式。 TestCase应该可以在鼻子或pytest或其他测试环境中使用。

– Guettli
2014年4月29日在12:21

#2 楼

使用unittest(从3.4开始)

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

请参阅文档:


26.4.7。使用子测试区分测试迭代
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):


使用鼻子

鼻子测试框架支持此功能。

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

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


评论


这是一种动态生成测试用例的非常干净的方法。

–很棒
15年7月29日在19:11

为更新部分投票。正是我所需要的。 :)

– Saurabh Shrivastava
18年6月25日在12:04

有没有一种方法可以使用pytest运行unittest版本,以便它可以在所有情况下运行并且不会在第一个失败的参数上停止?

–kakk11
19年4月24日在17:13

正如@ kakk11所提到的,此答案(和一般的subTest)不适用于pytest。这是一个已知的问题。有一个积极开发的插件可以使它工作:github.com/pytest-dev/pytest-subtests

–Jérémie
19年6月17日15:47



存在两个问题:1)第一次失败的子测试导致后续子测试无法运行2)子测试未调用setUp()和tearDown()

– V.K.
19年9月7日在18:30

#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)。将它们放在“元类= TestSequenceMeta”语句之后似乎有效。

–爱与和平-Joe Codeswell
2015年9月8日23:37

此解决方案优于被选为恕我直言的解决方案。

– petroslamb
16年1月18日在13:42

@petroslamb当定义了类本身而不是创建第一个实例时,将调用元类中的__new__方法。我可以想象这种动态创建测试方法的方法与unittest用于确定一个类中有多少个测试的自省更兼容(即它可以在创建该类的实例之前先编译测试列表)。

– BillyBBone
16-2-23在4:20

注意:在python 3中,将其更改为:class TestSequence(unittest.TestCase,metaclass = TestSequenceMeta):[...]

– Mathieu_Du
16 Dec 30 '18:40



您能用dct代替dict吗?将关键字用作变量名会造成混淆并且容易出错。

– npfoss
18年11月26日在4:26



#4 楼

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


测试运行的输出为:

======================================================================
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及更高版本。

–马克斯·马里什(Max Malysh)
15年12月20日在14:14

使用unittest2,它也可用于Python 2.7。

–伯恩哈德
16 Mar 25 '16在21:21

这种方法与进行单独测试之间的主要区别在于,测试状态不会每次都重置。 (也就是说,setUp()和tearDown()不在子测试之间运行。)

–凯文·克里斯托弗·亨利(Kevin Christopher Henry)
16年4月9日在15:16

@KevinChristopherHenry是的,但是理论上可以在子测试中手动调用self.setUp()。至于tearDown,在末尾自动调用它可能就足够了。

–触角
17年5月1日在20:33



我认为,当与上述元类方法结合使用时,此功能可能会很强大。

–内森·查佩尔(Nathan Chappell)
6月2日9:25

#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


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

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

评论


上面的代码失败:TypeError:__init __()最多接受2个参数(给定4个)

–max
2015年11月17日下午5:31

向构造函数的额外参数添加了null默认值。

–哈维尔
15年11月24日在1:56

我更喜欢@mojo答案中的鼻子参数化代码,但是对于我的客户而言,避免额外的依赖关系太有用了,因此我将其用于他们。

–圣人
16年3月19日在15:55

此解决方案是此页面上我最喜欢的。当前最高答案中建议的Nose及其前叉Nose2都仅用于维护,后者建议用户改用pytest。真是一团糟-我将坚持这样的本机方法!

– Sean
18年1月31日在22:00

红利:重新定义shortDescription方法的能力,用于通过参数传递的输出

– fun_vit
18年2月14日在10:30

#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
16 Mar 25 '16在15:13

@timgeb很高兴为您提供帮助。查看py.test标记,以获取更多示例。另外,我建议使用hamcrest通过人类可读的mutcher向您的断言中添加一些糖,可以根据自己的方式对其进行修改,组合或创建。另外,我们还提供了allure-python,它是py.test的漂亮报告生成

–谢尔盖·沃罗涅日斯基(Sergey Voronezhskiy)
16 Mar 25 '16在15:55

谢谢。我刚刚开始从unittest转到py.test。我曾经拥有TestCase基类,这些基类能够动态创建带有不同参数的子代,并将它们存储为类变量...这有点笨拙。

–timgeb
16 Mar 25 '16在15:57

@timgeb是的,您是对的。 py.test最致命的功能是yield_fixtures。可以进行设置,将一些有用的数据返回到测试中,并在测试结束后进行拆卸。夹具也可以被参数化。

–谢尔盖·沃罗涅日斯基(Sergey Voronezhskiy)
16-3-25在16:08



#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 楼

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

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

评论


我不能在unittest类中使用@given()宏。

–约翰·格林
18年8月11日在22:26

检查一下:hypothesis.readthedocs.io/en/master/…

–哈维尔
18年8月12日在19:16

#10 楼

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

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

并不是每个测试都可以具有自定义的setup函数。

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


还可以像通过其内置插件nosetest一样传递attrib参数,这样您就可以只运行带有特定参数的特定测试:

nosetest -a number=2


评论


我喜欢这种方法,尤其是它支持的每个方法级别。

–马特
15年3月6日在1:29

#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_stackoverflow.TestSequence) ... test bar
FAIL
testsample_u'foo'_u'a'_u'a' (tests.example.test_stackoverflow.TestSequence) ... test foo
ok
testsample_u'lee'_u'b'_u'b' (tests.example.test_stackoverflow.TestSequence) ... test lee
ok


也可以使用发电机。例如,此代码生成带有参数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_stackoverflow.TestSequence) ... ok
testsample_a(u'a')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'a') (tests.example.test_stackoverflow.TestSequence) ... FAIL
testsample_a(u'b')_b(u'b') (tests.example.test_stackoverflow.TestSequence) ... ok


#12 楼

前几天,我在看ra的源代码时遇到了ParamUnittest(github回购中的示例用法)。它应该与其他扩展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)


#13 楼

import unittest

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

def add_test_methods(test_class):
    #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
17年1月13日在19:12

@Raychaser ...您是正确的。.我已修复该问题,但未在此处进行更新..谢谢。

–阿林丹(Arindam Roychowdhury)
17年1月16日在10:45

#14 楼

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

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


#15 楼

您可以使用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__函数。

– Jadelord
17年8月2日在19:42

#16 楼

我发现这很适合我的目的,尤其是当我需要生成对一组数据进行稍微不同的处理的测试时。

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接口的实现。

#17 楼

此解决方案适用于适用于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的升级版本<3!

–拖把
4月26日7:56

#18 楼

我在使用非常特殊的参数化测试样式时遇到了麻烦。我们所有的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的连接。

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

#19 楼

基于元类的答案在Python3中仍然有效,但必须使用__metaclass__参数代替metaclass属性,例如:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass


#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的答案:拥抱基于属性的测试。假设库:


“比我们单纯的人类更不懈地曲折测试案例生成”
将提供简单的计数示例
与任何测试运行者一起工作
/>
具有更多有趣的功能(统计信息,附加测试输出...)

类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"))


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


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

有趣的是例子

#21 楼

派对晚了,但是我在setUpClass上做这些工作时遇到了麻烦。

这是@Javier答案的一个版本,可以让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 楼

只是在混合中抛出其他解决方案;)

这实际上与上述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):
        ...


#23 楼

除了使用setattr之外,我们还可以从python 3.2开始使用load_tests。请参阅博客文章blog.livreuro.com/en/coding/python/how-to-generate-discoverable-unit-tests-in-python-dynamically/

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


#24 楼

以下是我的解决方案。在以下情况下,我认为这很有用:
1。应该适用于unittest.Testcase和unittest discover
2。有一组针对不同参数设置运行的测试。
3。非常简单,无需依赖其他软件包
导入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


评论


这并不能回答有关即时生成测试的问题。

– Lenz
18年1月4日在20:14

#25 楼

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)


评论


好像您在那里丢失了格式。就目前而言,这真的很难阅读

– Arturo
19/12/6在0:44