У меня есть какие-то тестовые данные, и я хочу создать модульный тест для каждого элемента. Моя первая идея заключалась в следующем:
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()
Обратной стороной этого является то, что он обрабатывает все данные в одном тесте. Я хотел бы на лету сгенерировать по одному тесту для каждого элемента. Какие-либо предложения?
Хорошая ссылка, которая может дать ответ: eli.thegreenplace.net/2014/04/02/…






Это называется «параметризацией».
Есть несколько инструментов, поддерживающих этот подход. Например.:
Полученный код выглядит так:
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()
На самом деле, большой, этот код ДЕЙСТВИТЕЛЬНО генерирует разные имена для каждого теста (иначе он бы не работал). В приведенном примере выполняемые тесты будут называться «test_foo», «test_bar» и «test_lee» соответственно. Таким образом, упомянутое вами преимущество (и оно очень большое) сохраняется до тех пор, пока вы генерируете разумные имена.
Как говорится в ответе @codeape, с этим справляется нос. Однако, кажется, что Нос не поддерживает Юникод; поэтому для меня это предпочтительное решение. +1
Как говорится в ответе @codeape ниже, у носа есть поддержка для этого. Однако, похоже, он не очень хорошо поддерживает Unicode. Поэтому этот ответ предпочтительнее для тех, кто запускает тесты с данными Unicode. +1
Проблема в том, что когда вы не можете сделать python myfile.py TestSequense.test_foo - вы получите ValueError: no such test method in <class '__main__.TestSequense'>: test_generator, поэтому вы можете сделать только python myfile.py TestSequense.
Обратите внимание, что более правильный ответ дан в вопросе дублировать: stackoverflow.com/a/2799009/322020 - вы используете .__name__ = для включения тестирования .exact_method.
Почему код, изменяющий класс, появляется в условном выражении if __name__ == '__main__'? Конечно, он должен выходить за рамки этого, чтобы запускаться во время импорта (помня, что модули python импортируются только один раз, даже если они импортированы из нескольких разных мест)
@SpoonMeiser, это был всего лишь образец, конечно, есть масса способов улучшить ситуацию. спасибо за внимание
Не думаю, что это хорошее решение. Код unittest не должен зависеть от способа его вызова. TestCase должен использоваться в носу, pytest или другой тестовой среде.
@mojo - Как запустить только один тест, если вы используете нос для параметризованных тестов? Я использую pycharm IDE. Когда я щелкаю правой кнопкой мыши и запускаю параметризованный тест носа, я получаю ошибку Error Traceback (most recent call last): File "C:\Python3\lib\unittest\case.py", line 384, in _executeTestPart function() TypeError: 'NoneType' object is not callable. Итак, мне нужно запустить все тесты, чтобы проверить один тест. Как я могу это исправить ?
@BoratSagdiyev, я ответил на этот вопрос 8 лет назад :) Боюсь, что я уже не специалист в этом вопросе. Кроме того, бит nose_parameterized был добавлен кем-то другим. Извини!
У меня сложилось впечатление, что 10 лет назад это был отличный ответ. Но он получил столько голосов, потому что это самый старый, а не лучший ответ. В настоящее время, вероятно, лучше всего использовать параметризованный декоратор py.test. (Метаклассы уродливы и не работают с py.test; многие одноразовые пакеты больше не разрабатываются и не поддерживаются.)
@ Jérémie Я согласен, вы хотите, чтобы я обновил ответ?
Я завернул его в статическую функцию, которую я добавил в свое определение TestCase, под названием addTest (cls, id, func_name, params, description). Он работает хорошо, но тест должен быть добавлен в глобальную область видимости (не в разделе "if имя ==" главный ", поэтому участники тестирования могут загрузить модуль и также найти тесты. Например: myclass.addTest (1," test_equal) ", (" a "," b ")," Тестовые значения a и b равны ") Следующим шагом будет построение цикла вокруг этого и получение данных из списка. Не уверен, почему unittest не имеет этой функции уже.
@BrookeWallace, это был демонстрационный код для ответа, опубликованного в 2008 году. Теперь, согласно другим высокопоставленным ответам, для этого есть лучшие инструменты, в том числе что-то в модульном тестировании.
Использование модульный тест (начиная с 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)
Это очень чистый способ динамического создания тестовых примеров.
Но имейте в виду, что 'setup ()' не будет знать, какие переменные используются в качестве аргументов для уступки. На самом деле setup () не будет знать, какой тест запущен, или переменные, установленные внутри test_generator (). Это усложняет проверку работоспособности в setup () и является одной из причин, по которой некоторые люди предпочитают py.test.
Это работает! но тогда вы не можете использовать unittest.TestCase и его семейства методов assert *.
Собственно, можете. Я просмотрел документы unittest, и это было добавлено в 3.4 (я обновил ответ с помощью примера кода).
Проголосовали за раздел обновления. Именно то, что мне нужно. :)
Я отредактировал ответ, теперь информация о модуле находится вверху, где и принадлежит.
Есть ли способ запустить версию unittest с помощью pytest, чтобы она запускала все случаи и не останавливалась на первом неудачном параметре?
Как упоминалось @ kakk11, этот ответ (и подтест в целом) не работает с pytest. Это известная проблема. Для этой работы существует активно развивающийся плагин: github.com/pytest-dev/pytest-subtests
есть две проблемы: 1) первый неудачный субтест приводит к тому, что последующие субтесты не запускаются; 2) setUp() и tearDown() не вызываются для субтестов
Вам будет полезно попробовать библиотеку TestScenarios.
testscenarios provides clean dependency injection for python unittest style tests. This can be used for interface testing (testing many implementations via a single test suite) or for classic dependency injection (provide tests with dependencies externally to the test code itself, allowing easy testing in different situations).
Это можно элегантно решить с помощью метаклассов:
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()
очень хорошо, столкнулся с различными проблемами с setattr, это просто работает!
Это отлично сработало для меня с Selenium. В качестве примечания, в классе TestSequence вы можете определить «статические» методы, такие как setUp (self), is_element_present (self, how, what), ... tearDown (self). Размещение их ПОСЛЕ оператора "метакласс = TestSequenceMeta", похоже, работает.
ИМХО это решение лучше, чем выбранное как принятое.
Не могли бы вы прояснить, почему это не работает, если просто переопределить __new__ из класса TestSequence (и, таким образом, полностью пропустить метакласс)?
@petroslamb Метод __new__ в метаклассе вызывается при определении самого класса, а не при создании первого экземпляра. Я бы предположил, что этот метод динамического создания методов тестирования более совместим с интроспекцией, используемой unittest для определения количества тестов в классе (т.е. он может составить список тестов до того, как когда-либо создаст экземпляр этого класса).
Для меня это работает лучше, чем принятый ответ: обнаружение теста не работает должным образом с другим ответом, но работает с этим.
Примечание: в python 3 измените это на: class TestSequence(unittest.TestCase, metaclass=TestSequenceMeta):[...]
... и для поддержки Python 2 и 3 см. python-future.org/compatible_idioms.html#metaclasses
Полезно отметить, что это работает только при определении __new__, но не __init__. Это работает намного лучше, чем принятый ответ IMO, потому что он будет работать, даже когда unittest импортируется или добавляется в набор тестов.
Не могли бы вы использовать dct вместо dict? Использование ключевых слов в качестве имен переменных сбивает с толку и чревато ошибками.
Очень хорошо работал в ситуации, когда у меня было несколько регулярных выражений, и я знал, каким должен быть match.groupdict для каждого положительного / отрицательного случая. Мне это не понадобилось, но хорошо знать, что вы можете передавать произвольные параметры ключевого слова в функцию метакласса __new__.
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
Приведенный выше код не работает: TypeError: __init __ () принимает не более 2 аргументов (4 задано)
Добавлены нулевые значения по умолчанию для дополнительных параметров конструктора.
Я предпочитаю код параметризации носа в @ Mojo ответ, но для моих клиентов он слишком полезен, чтобы избежать дополнительной зависимости, поэтому я буду использовать его для них.
Это решение было моим любимым на этой странице. И Нос, предложенный в текущем верхнем ответе, и его вилка Нос2 предназначены только для обслуживания, и последний предлагает пользователям вместо этого попробовать pytest. Какой беспорядок - я буду придерживаться такого нативного подхода!
Единственный недостаток, который я обнаружил, заключается в том, что сторонние участники тестирования иногда не поддерживают этот механизм, поскольку они не знали о нем.
бонус: возможность переопределить метод shortDescription для вывода, переданного в параметрах
Это можно сделать с помощью 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 и т. д.
Я искал простой и понятный пример параметризации тестовых примеров с помощью py.test. Большое спасибо!
@timgeb Рад помочь вам. Проверьте тег py.test, чтобы увидеть больше примеров. Также я предлагаю использовать Hamcrest для добавления сахара в ваши утверждения с помощью удобочитаемых мутчеров, которые можно изменять, комбинировать или создавать по своему усмотрению. Плюс у нас есть очарование-питон, красивый генератор отчетов для py.test.
Спасибо. Я только начал переходить с unittest на py.test. Раньше у меня были базовые классы TestCase, которые могли динамически создавать дочерние элементы с разными аргументами, которые они сохраняли как переменные класса ... что было немного громоздко.
@timgeb Ага, ты прав. Наибольшее значение Убийственная особенность для py.test - это yield_fixtures. Что может сделать настраивать, вернуть некоторые полезные данные в тест и после завершения теста сделать срывать. Светильники также могут быть параметризованный.
Вы можете использовать плагин нос-иттр (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
Мне нравится этот подход, особенно поддерживаемый им уровень метода.
На днях я наткнулся на 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)
Начиная с 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.
Лучшее решение, если вы используете Python 3.4 и выше.
Используя unittest2, это также доступно для Python 2.7.
Одно из основных различий между этим подходом и отдельными тестами заключается в том, что состояние теста не сбрасывается каждый раз. (То есть setUp() и tearDown() не запускаются между субтестами.)
@KevinChristopherHenry: Да, но self.setUp() теоретически можно вызвать вручную из подтеста. Что касается tearDown, может быть достаточно его автоматического вызова в конце.
Я думаю, что это может быть мощным, если использовать в сочетании с подходом метакласса, описанным выше.
Используйте библиотеку 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.
Просто используйте метаклассы, как показано здесь;
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
Я использую метаклассы и декораторы для генерации тестов. Вы можете проверить мою реализацию 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
Также есть Гипотеза, который добавляет тестирование на основе нечеткости или свойств.
Это очень мощный метод тестирования.
Я не мог использовать макрос @given() внутри класса unittest.
Проверьте это: hypothesis.readthedocs.io/en/master/…
Вы можете использовать классы 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__.
У меня были проблемы с очень специфическим стилем параметризованных тестов. Все наши тесты 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.
В любом случае, я надеюсь, что это может помочь тем, кто хочет провести аналогичную «глобальную» параметризацию своих тестов!
Помимо использования 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()
Ссылка битая (DNS?): «Хм. У нас возникли проблемы с поиском этого сайта. Мы не можем подключиться к серверу на сайте blog.livreuro.com».
Вот мое решение. Я считаю это полезным, когда:
Должен работать для unittest.Testcase и unittest обнаружить
Имейте набор тестов, которые нужно запустить для различных настроек параметров.
Очень просто и не зависит от других пакетов
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
Это не отвечает на вопрос о генерации тестов на лету.
Это решение работает с 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 за обновленную версию
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)
РЕЗУЛЬТАТ:
>>>
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)
Незначительная проблема с функцией def add_test_methods. Думаю, должен быть def _add_test_methods
@Raychaser ... Вы правы ... Я исправил это, но не обновлял здесь .... Спасибо, что уловили это.
Ответы на основе метаклассов по-прежнему работают в Python 3, но вместо атрибута __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()
Поскольку мы не должны повторяться, мое второе предложение основано на Ответ Хавьера: используйте тестирование на основе свойств. Библиотека гипотез:
«более неумолимо коварен в создании тестовых примеров, чем мы, простые люди»
предоставит простые примеры подсчета
работает с любым тестовым раннером
имеет еще много интересных функций (статистика, дополнительный тестовый результат, ...)
класс 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
Я обнаружил, что это хорошо работает для моих целей, особенно если мне нужно сгенерировать тесты, которые немного различают процессы в сборе данных.
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.
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)
вроде как потерял там форматирование. это действительно трудно читать в нынешнем виде
Объяснение было бы в порядке.
Это фактически то же самое, что и 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):
...
возможный дубликат Python unittest: программно создавать несколько тестов?