Как вы генерируете динамические (параметризованные) модульные тесты в Python? - PullRequest
189 голосов
/ 28 августа 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()

Недостатком этого является то, что он обрабатывает все данные в одном тесте. Я хотел бы создать один тест для каждого элемента на лету. Есть предложения?

Ответы [ 22 ]

2 голосов
/ 06 марта 2015

Я сталкивался 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)
2 голосов
/ 01 июля 2015

Я использую метаклассы и декораторы для генерации тестов. Вы можете проверить мою реализацию 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__list и b__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
1 голос
/ 20 декабря 2015

Вы можете использовать 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)
1 голос
/ 07 декабря 2016
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)
1 голос
/ 10 мая 2015

Просто используйте метаклассы, как показано здесь;

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
0 голосов
/ 26 января 2018

Супер поздно на вечеринку, но у меня были проблемы с тем, чтобы заставить их работать на 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
0 голосов
/ 08 августа 2017

Мета-программирование - это весело, но можно начать. Большинство решений здесь затрудняют:

  • выборочно запустить тест
  • указать код, указанный в названии теста

Итак, мое первое предложение - следовать простому / явному пути (работает с любым участником тестирования):

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

Хорошо, у вас нет разных имен тестов. Но, может быть, вам просто нужно:

  • описательное название тестируемого объекта.
  • , какой вход приводит к сбою (пример фальсификации).

Более смешной пример

0 голосов
/ 08 мая 2017

Ответы на основе метаклассов по-прежнему работают в Python3, но вместо атрибута __metaclass__ необходимо использовать параметр metaclass, например:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass
0 голосов
/ 06 сентября 2016

Это решение работает с unittest и nose:

#!/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()
0 голосов
/ 02 августа 2016

Следующее - мое решение. Я считаю это полезным, когда: 1. Должен работать для unittest.Testcase и unittest обнаружить 2. Иметь набор тестов для разных настроек параметров. 3. Очень просто, нет зависимости от других пакетов импортный тест

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