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

如何在Python中生成动态(参数化)单元测试?,python,unit-testing,parameterized-unit-test,Python,Unit Testing,Parameterized Unit Test,我有一些测试数据,希望为每个项目创建一个单元测试。我的第一个想法是这样做: import unittest l = [["foo", "a", "a",], ["bar", "a", "b"], ["lee", "b", "b"]] class TestSequence(unittest.TestCase):

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

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()
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)
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()
这样做的缺点是它在一次测试中处理所有数据。我想为每个项目动态生成一个测试。有什么建议吗?

这叫做“参数化”

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

生成的代码如下所示:

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()
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)
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\u sequence\u 0\u foo(\uuu main\uuuu.TestSequence)。。。好啊
测试顺序1个测试条(uuuu main_uuuuuu.TestSequence)。。。失败
测试序列2李(uuu main_uuu.TestSequence)。。。好啊
======================================================================
失败:测试顺序\u 1 \u条(\u主\u.TestSequence)
----------------------------------------------------------------------
回溯(最近一次呼叫最后一次):
文件“/usr/local/lib/python2.7/site packages/parameterized/parameterized.py”,第233行,在
独立函数=lambda*a:func(*(a+p.args),**p.kwargs)
文件“x.py”,第12行,按测试顺序
自我评估资格(a、b)
断言者错误:“a'!=”b'
出于历史原因,我将保留原始答案(大约2008年):

我用这样的方式:

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()
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)
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()
使用(从3.4开始)

自Python 3.4以来,标准库
unittest
包具有
子测试
上下文管理器

请参阅文档:

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)
例如:

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)
您还可以为
子测试()
指定自定义消息和参数值:

使用

测试框架

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

nosetests命令的输出:

>鼻测试-v
testgen.test_生成器('a','a')。。。好啊
testgen.test_生成器('a','b')。。。失败
testgen.test_生成器('b','b')。。。好啊
======================================================================
失败:testgen.test_生成器('a','b')
----------------------------------------------------------------------
回溯(最近一次呼叫最后一次):
runTest中的文件“/usr/lib/python2.5/site packages/nose-0.10.1-py2.5.egg/nose/case.py”,第203行
自检(*self.arg)
文件“testgen.py”,第7行,在check\u em中
断言a==b
断言错误
----------------------------------------------------------------------
在0.006秒内运行了3次测试
失败(失败=1)

您将从尝试该库中获益

testscenarios为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()
是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
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)
该代码将运行load_测试返回的TestSuite中的所有测试用例。发现机制不会自动运行其他测试

或者,您也可以使用此票证中所示的继承:

可以使用。只需编写包含以下内容的文件
test_me.py

导入pytest
@pytest.mark.parametize('name,left,right',[[['foo','a','a'],
['bar','a','b'],
['baz','b','b']]
def测试名称(名称、左、右):
断言left==right,name
并使用命令
py.test--tb=shorttest\u me.py
运行测试。然后输出将如下所示:

===========================================测试会话开始============================
平台darwin--Python2.7.6--py-1.4.23--pytest-2.6.1
收集了3个项目
测试我的能力。
===================================================故障=================================
_____________________________测试我[bar-a-b]_____________________________
测试我。py:8:测试我
断言left==right,name
E断言者错误:酒吧
======================1失败,2在0.01秒内通过====================
这很简单!还有更多的功能,如
fixtures
mark
assert
,等等。

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

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

请注意,您还可以在每个测试中使用自定义
设置
功能

@ittr(number=[1, 2, 3, 4])
def test_even(self):
    assert_equal(self.number % 2, 0)
它还可以通过
nosetest
参数,就像其内置插件
attrib
一样。这样,您只能使用特定参数运行特定测试:

nosetest-一个数字=2
前几天我在查看()的源代码时偶然发现。它应该与其他扩展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)
导入单元测试
导入参数单元测试
@参数化(
('1', '2'),

#(4,3),从Python 3.4开始,unittest中引入了子测试。有关详细信息,请参阅。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)
测试运行的输出将是:

======================================================================
失败:测试为偶数(uuuu main_uuuuu.NumbersTest)(i=1)
----------------------------------------------------------------------
回溯(最近一次呼叫最后一次):
文件“subtests.py”,第32行,在test_中
self.assertEqual(i%2,0)
断言错误:1!=0
=========================================
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
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()
 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
#!/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()
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)
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()
  @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.just("a"), st.just("b"))
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
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()
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)
class TestStuff(unittest.TestCase):
    @sub_test([
        dict(arg1='a', arg2='b'),
        dict(arg1='x', arg2='y'),
    ])
    def test_stuff(self, a, b):
        ...