Как вы генерируете динамические (параметризованные) модульные тесты в Python?

235

У меня есть какие-то тестовые данные и я хочу создать модульный тест для каждого элемента. Моей первой идеей было сделать это так:

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

Ответы:

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 должен быть пригоден для использования в носу или в pytest или в другой тестовой среде.
Геттли
147

Использование 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)
codeape
источник
3
Это очень чистый способ динамического создания тестовых случаев.
Габористый
Но учтите, что setup () не будет знать, какие переменные используются в качестве аргументов для выхода. На самом деле setup () не будет знать, какой тест выполняется, или переменные, установленные внутри test_generator (). Это усложняет проверку работоспособности в setup (), и это одна из причин, по которой некоторые люди предпочитают py.test.
Скотт Прайв
1
Проголосовал за обновление раздела. Именно то, что мне было нужно. :)
Саурабх Шривастава
1
Есть ли способ запустить версию unittest с помощью pytest, чтобы он запускал все случаи и не останавливался на первом неудачном параметре?
kakk11
1
Как упоминается @ kakk11, этот ответ (и subTest в целом) не работает с pytest. Это известная проблема. Есть активно разработанный плагин, чтобы сделать эту работу: github.com/pytest-dev/pytest-subtests
Jérémie
76

Это можно решить элегантно, используя метаклассы:

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
Это сработало для меня с Selenium. Как примечание, в классе TestSequence вы можете определить «статические» методы, такие как setUp (self), is_element_present (self, how, what), ... tearDown (self). Положив их ПОСЛЕ « метаклассом оператора = TestSequenceMeta» , кажется, работает.
Любовь и мир - Джо Кодсвелл
5
Это решение лучше, чем выбранное в качестве принятого ИМХО.
petroslamb
2
@petroslamb __new__Метод в метаклассе вызывается при определении самого класса, а не при создании первого экземпляра. Я полагаю, что этот метод динамического создания тестовых методов более совместим с самоанализом, используемым unittestдля определения количества тестов в классе (то есть он может составить список тестов до того, как он создаст экземпляр этого класса).
BillyBBone
11
Примечание: в питоне 3 измените это на:class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
Mathieu_Du
3
Не могли бы вы использовать dctвместо dict? Использование ключевых слов в качестве имен переменных сбивает с толку и подвержено ошибкам.
npfoss
49

Начиная с 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)

Результат тестового прогона будет:

======================================================================
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автоматического вызова в конце, может быть достаточно.
Acumenus
Я думаю, что это может быть мощным, когда используется в сочетании с метаклассовым подходом выше.
Натан Чаппелл
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

Этот код будет запускать все TestCases в TestSuite, возвращенном load_tests. Никакие другие тесты не запускаются автоматически механизмом обнаружения.

Кроме того, вы также можете использовать наследование, как показано в этом билете: http://bugs.python.org/msg151444

Хавьер
источник
1
Приведенный выше код завершается ошибкой: TypeError: __init __ () принимает не более 2 аргументов (задано 4)
максимум
2
Добавлены нулевые значения по умолчанию для дополнительных параметров конструктора.
Хавьер
Я предпочитаю код параметризации носа в ответе @ mojo , но для моих клиентов слишком полезно избегать лишних зависимостей, поэтому я буду использовать это для них.
Мудрец
1
Это решение было моим любимым на этой странице. И Nose , предложенный в текущем топ-ответе, и его форк Nose2 предназначены только для обслуживания, и последний предлагает пользователям вместо этого попробовать pytest . Какой беспорядок - я буду придерживаться такого подхода!
Шон
1
бонус: возможность переопределения метода shortDescription для выходных данных, передаваемых в параметрах
fun_vit
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 , чтобы увидеть больше примеров. Также я предлагаю использовать hamcrest для добавления сахара в ваши утверждения с помощью читабельных человеком мульчеров, которые можно модифицировать, комбинировать или создавать по-своему. Плюс у нас есть allure-python , красивая генерация отчетов дляpy.test
Сергей Воронежский
Спасибо. Я только начал переходить от unittestpy.test. Раньше у меня были TestCaseбазовые классы, которые могли динамически создавать дочерние элементы с различными аргументами, которые они сохраняли бы как переменные класса ... что было немного громоздко.
Timgeb
1
@timgeb Да, ты прав. Наиболее убийца особенность в py.testэтом yield_fixtures . Который может выполнить настройку , вернуть некоторые полезные данные в тест и после окончания теста произвести демонтаж . Светильники также могут быть параметризованы .
Сергей Воронежский
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 .

testscenarios обеспечивает чистую инъекцию зависимостей для тестов стиля Python unittest. Это может использоваться для тестирования интерфейса (тестирование многих реализаций с помощью одного набора тестов) или для классического внедрения зависимостей (предоставляют тесты с зависимостями извне к самому тестовому коду, что позволяет легко тестировать в различных ситуациях).

большой нос
источник
5

Есть также гипотеза, которая добавляет нечеткое или основанное на свойствах тестирование: https://pypi.python.org/pypi/hypothesis

Это очень мощный метод тестирования.

Хавьер
источник
Я не мог использовать @given()макрос внутри класса unittest.
Джон Грин
1
Проверьте это: hypothesis.readthedocs.io/en/master/…
Хавьер
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)
Matt
источник
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
sleepycal
источник
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интерфейса.

bcdan
источник
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), и у каждого из них есть свой собственный параметр. Платформа для запуска, даже в 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()

Поскольку мы не должны повторяться, мое второе предложение основано на ответе @ 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"))

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

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

Забавный пример

YvesgereY
источник
0

Очень поздно на вечеринку, но у меня были проблемы с этим setUpClass.

Вот версия ответа @ Javier, которая дает 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
hhquark
источник
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()
pptime
источник
-1

Следующее - мое решение. Я нахожу это полезным, когда: 1. Должно работать для unittest.Testcase и unittest обнаружить 2. Иметь набор тестов для разных настроек параметров. 3. Очень просто, нет зависимости от других пакетов импорта 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
S.Arora
источник
Это не отвечает на вопрос о генерации тестов на лету.
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)
thangaraj1980
источник
кажется, что вы потеряли форматирование там. это действительно трудно читать в его нынешнем виде
Артуро