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

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

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

有帮助吗?

解决方案

我用这样的东西:

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

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'

其他提示

使用单元测试(自 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)

您还可以指定自定义消息和参数值 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)

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

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

为此,从 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

这也是一部分 单元测试2, ,因此它可用于早期版本的 Python。

负载测试 是 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 中的所有测试用例。发现机制不会自动运行其他测试。

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

可以通过使用来完成 py测试. 。只需写入文件即可 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 ====================

很简单!还 py测试 有更多功能,例如 fixtures, mark, assert, , ETC ...

使用 滴滴涕 图书馆。它为测试方法添加了简单的装饰器:

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 模块。

您将受益于尝试 测试场景 图书馆。

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

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

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

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

我碰到 参数单元测试 前几天看源码的时候 (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)

我使用元类和装饰器来生成测试。你可以检查我的实现 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

只需使用元类,如下所示;

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

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

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

有了这个,我所要做的就是向每个常规的旧测试用例添加一个简单的装饰器 @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 的连接的地方。

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

该解决方案适用于 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()

基于元类的答案在 Python3 中仍然有效,但不再是 __metaclass__ 属性一必须使用 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):

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

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

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

更有趣的例子

参加派对已经太晚了,但我很难让这些发挥作用 setUpClass.

这是一个版本 @哈维尔的回答 这给了 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

除了使用 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()

以下是我的解决方案。我发现这在以下情况下很有用:1.应该适用于UNITSEST.TESTCASE和UNITSEST DISCOVE 2。针对不同的参数设置运行一组测试。3.非常简单的不依赖其他软件包导入UNITSEST

    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
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top