Як ви генеруєте динамічні (параметризовані) тести одиниць у python?


234

У мене є якісь дані тесту і хочу створити одиничний тест для кожного елемента. Першою моєю ідеєю було зробити це так:

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

Мінус цього полягає в тому, що він обробляє всі дані за один тест. Я хотів би створити один тест для кожного предмета на льоту. Будь-які пропозиції?



2
Гарне посилання, яке може дати відповідь: eli.thegreenplace.net/2014/04/02/…
робочий

Відповіді:


173

Це називається "параметризація".

Існує кілька інструментів, які підтримують такий підхід. Наприклад:

Отриманий код виглядає приблизно так:

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'

З історичних причин я залишу оригінальну відповідь близько 2008 р.):

Я використовую щось подібне:

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

24
Власне, bignose, цей код НЕ генерує іншу назву для кожного тесту (він насправді не працював би інакше). У наведеному прикладі виконані тести будуть названі відповідно "test_foo", "test_bar" та "test_lee". Таким чином, користь, яку ви згадуєте (і вона є великою), зберігається до тих пір, поки ви генеруєте розумні імена.
Тоджі

1
Як відповідає відповідь @codeape, ніс це вирішує. Однак ніс, здається, не справляється з Unicode; тому для мене це краще рішення. +1
Кіт Пінсон

5
Так відзначити, що більш правильну відповідь дається в дубліката питання: stackoverflow.com/a/2799009/322020 - ви повинні використовувати , щоб .__name__ =для включення .exact_methodтестування
Nakilon

7
Чому код, що модифікує клас, відображається в if __name__ == '__main__'умовному? Безумовно, це має вийти за межі цього, щоб запустити час імпорту (пам’ятаючи, що модулі python імпортуються лише один раз, навіть якщо вони імпортуються з кількох різних місць)
SpoonMeiser

4
Я не думаю, що це хороше рішення. Код одиничного тесту не повинен залежати від способу його виклику. TestCase повинен бути застосований в носі, пістесті або в інших тестових умовах.
guettli

146

Використання unittest (починаючи з 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)

3
Це дуже чистий спосіб динамічного генерування тестових випадків.
габоровий

Але врахуйте, що "setup ()" не буде знати, які змінні використовуються як аргументи для отримання. Насправді setup () не буде знати, який тест запущений, або параметри встановлені всередині test_generator (). Це ускладнює перевірку рівня безпеки під час встановлення (), і це одна з причин того, що деякі люди віддають перевагу py.test.
Скотт-Прі

1
Запропоновано для розділу оновлення. Саме те, що мені було потрібно. :)
Саурабх Шрівастава

1
Чи є спосіб запустити unittest версію pytest, щоб він запустив усі випадки і не зупинився на першому невдалому параметрі?
kakk11

1
Як згадував @ kakk11, ця відповідь (і підтест загалом) не працює з pytest. Це відоме питання. Є активно розроблений плагін, щоб зробити цю роботу: github.com/pytest-dev/pytest-subtests
Jérémie

76

Це можна вирішити елегантно за допомогою Metaclasses:

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

1
Це спрацювало ВЕЛИКО для мене з Селеном. Як зауваження, у класі TestSequence ви можете визначити "статичні" методи, такі як setUp (self), is_element_present (self, how, what), ... tearDown (self). Поміщення їх ПІСЛЯ оператора " metaclass = TestSequenceMeta", здається, працює.
Любов і спокій - Джо Кодесвелл

5
Це рішення краще, ніж обране як прийняте IMHO.
петрослам

2
@petroslamb __new__Метод у метакласі викликається, коли визначено сам клас, а не коли створено перший екземпляр. Я думаю, що цей метод динамічного створення методів тестування є більш сумісним із самоаналізом, який використовується unittestдля визначення кількості тестів у класі (тобто він може скласти список тестів до того, як коли-небудь створить екземпляр цього класу).
BillyBBone

11
Примітка: у python 3 змініть це на:class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du

3
Не могли б ви використати dctзамість dict? Використання ключових слів як імен змінних є заплутаним та схильним до помилок.
npfoss

49

Станом на 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.


1
Найкраще рішення, якщо ви використовуєте python 3.4 і вище.
Макс Малиш

4
Використовуючи unittest2, це також доступно для Python 2.7.
Бернхард

11
Однією з головних відмінностей між цим підходом та наявністю окремих тестів є те, що стан тесту не скидається кожного разу. (Тобто, setUp()і tearDown()не запускаються між субтестами.)
Кевін Крістофер Генрі

1
@KevinChristopherHenry Так, але self.setUp()теоретично їх можна викликати вручну з підтесту. Що стосується того tearDown, що його автоматично викликати в кінці, можливо, буде достатньо.
Акумен

Я думаю, що це може бути потужним, якщо використовувати його у поєднанні з підходом до метакласу вище.
Натан Чаппелл

36

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, повернені load_tests. Інші тести автоматично не виконуються механізмом виявлення.

Крім того, ви також можете використовувати спадщину, як показано в цьому квитку: http://bugs.python.org/msg151444


1
Код вище не вдається: TypeError: __init __ () бере максимум 2 аргументи (4 дані)
макс

2
Додані нульові параметри до додаткових параметрів конструктора.
Хав'єр

Я вважаю за краще код параметризації носа у відповіді @ mojo , але для моїх клієнтів це занадто корисно, щоб уникнути зайвої залежності, тому я буду використовувати це для них.
шавлія

1
Це рішення було моїм улюбленим на цій сторінці. І Нос , запропонований у поточній головній відповіді, і його вилка Nose2 є лише технічним обслуговуванням, і останній пропонує користувачам замість цього спробувати pytest . Який безлад - я буду дотримуватися такого рідного підходу!
Шон

1
бонус: можливість переосмислити короткий метод опису виходу, який пройшов у
парамах

33

Це можна зробити за допомогою 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 має більше функцій , таких як fixtures, mark, assertі т.д. ...


1
Я шукав простий, прямолінійний приклад, як параметризувати тестові випадки з py.test. Велике спасибі!
timgeb

@timgeb Я радий вам допомогти. Перевірте тег py.test , щоб отримати більше прикладів. Також я пропоную використовувати хокрест, щоб додати трохи цукру до ваших заяв із читаними мушками, які можна змінювати, комбінувати або створювати власним чином. Плюс у нас є allure-python , приємне покоління звітів дляpy.test
Сергій Воронезький,

Дякую. Я щойно почав переходити з unittestpy.test. У мене були TestCaseбазові класи, які могли динамічно створювати дітей з різними аргументами, які вони зберігатимуть як змінні класу ... що було трохи непростим.
timgeb

1
@timgeb Так, ви праві. Найбільш вбивча особливість - py.testце придатність_фіксації . Що може зробити налаштування , повернути деякі корисні дані в тест і після закінчення тестування здійснити спробу . Світильники також можна параметризувати .
Сергій Воронезький

12

Використовуйте бібліотеку 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модулем бібліотеки .


6

Ви б з користю спробували бібліотеку TestScenarios .

testcenarios забезпечує чисту ін'єкційну залежність для тестів стилю python unittest. Це може використовуватися для тестування інтерфейсу (тестування багатьох реалізацій за допомогою одного тестового набору) або для класичного введення залежності (надання тестів із залежностями зовнішньо до самого тестового коду, що дозволяє легко тестувати в різних ситуаціях).



4

Можна використовувати плагін nose-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

Мені подобається такий підхід, особливо на рівні методу, який він підтримує.
Метт

3

Я використовую метакласи та декоратори для генерування тестів. Ви можете перевірити мою реалізацію 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

2

Я натрапив на 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
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
Незначна проблема з вашою def add_test_methodsфункцією. Чи варто def _add_test_methods думати
Raychaser

@Raychaser ... Ви маєте рацію .. Я це виправив, але тут не оновлював його .... Дякую, що це зрозумів.
Аріндам Ройховдхурі

1

Просто використовуйте метакласи, як це видно тут;

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

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)

Поки TestSuite працює, аргументи не передаються у __init__функцію.
jadelord

1

Я виявив, що це добре працює в моїх цілях, особливо якщо мені потрібно генерувати тести, які дещо відрізняються процесами збору даних.

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інтерфейсу.


1

Це рішення працює з unittestі noseдля Python 2 та Python 3:

#!/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!
швабра

0

У мене були проблеми з дуже специфічним стилем параметризованих тестів. Всі наші тести 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 () до кожного звичайного старого TestCase, і тепер, коли вони запускаються, вони загортаються та переписуються, щоб усі методи тестування були параметризовані та перейменовані. 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.

У будь-якому випадку, я сподіваюся, що це може допомогти тому, хто хоче зробити подібну "глобальну" параметризацію своїх тестів!


0

Відповіді на основі метакласу все ще працюють у Python3, але замість __metaclass__атрибута треба використовувати metaclassпараметр, як у:

class ExampleTestCase(TestCase,metaclass=DocTestMeta):
    pass

0

Метапрограмування - це цікаво, але може стати на шляху. Більшість рішень тут ускладнює:

  • вибірково запустити тест
  • поверніться до коду, даного імені тесту

Отже, моя перша пропозиція - дотримуватися простого / явного шляху (працює з будь-яким тестовим бігуном):

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

Оскільки ми не повинні повторювати себе, моя друга пропозиція ґрунтується на відповіді @ Хав'єра: прийняти тестування на основі властивостей. Бібліотека гіпотез:

  • є "невблаганно хитрішим щодо генерування тестових випадків, ніж ми, просто люди"
  • наведемо прості приклади підрахунку
  • працює з будь-яким тестовим бігуном
  • має ще багато цікавих функцій (статистика, додатковий вихідний тест, ...)

    клас 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

Супер пізно на вечірку, але у мене виникли проблеми з тим, щоб зробити ці роботи 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

Просто кинути інший розчин у суміш;)

Це фактично те саме, 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):
        ...

-1

Крім використання setattr, ми можемо використовувати load_tests з python 3.2. Будь ласка, зверніться до публікації в блозі 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

Далі йде моє рішення. Я вважаю це корисним, коли: 1. Має працювати для unittest.Testcase та unittest виявити 2. Встановити набір тестів для різних параметрів параметрів. 3. Дуже просто, не залежно від інших пакетів імпорту unittesttest

    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

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

здається, ви там втратили форматування. насправді важко читати, як це стоїть
Артуро
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.