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

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 3中仍然有效,但必须使用元类参数,而不是__metaclass__属性,例如:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

其他回答

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

有选择地启动测试 指向给出测试名称的代码

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

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): st.text @given (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"))

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

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

有趣的例子

您将从尝试testscenario库中受益。

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

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

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

以下是我的解决方案。我发现这个方法很有用:

Should work for unittest.Testcase and unittest discover Have a set of tests to be run for different parameter settings. Very simple and no dependency on other packages 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

我在一种非常特殊的参数化测试风格上遇到了麻烦。我们所有的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

With this, all I had to do was add a simple decorator @sauce_labs() to each regular old TestCase, and now when running them, they're wrapped up and rewritten, so that all the test methods are parameterized and renamed. LoginTests.test_login(self) runs as LoginTests.test_login_internet_explorer_10.0(self), LoginTests.test_login_internet_explorer_11.0(self), and LoginTests.test_login_firefox_43.0(self), and each one has the parameter self.platform to decide what browser/platform to run against, even in LoginTests.setUp, which is crucial for my task since that's where the connection to SauceLabs is initialized.

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