Вопрос-Ответ

How do you generate dynamic (parameterized) unit tests in Python?

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

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

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

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

Переведено автоматически
Ответ 1

Это называется "параметризацией".

Существует несколько инструментов, поддерживающих этот подход. Например.:

Результирующий код выглядит следующим образом:

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()
Ответ 2

Using unittest (since 3.4)

Since Python 3.4, the standard library unittest package has the subTest context manager.

See the documentation:

Example:

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(p1, p2):
self.assertEqual(p1, p2)

You can also specify a custom message and parameter values to subTest():

with self.subTest(msg="Checking if p1 equals p2", p1=p1, p2=p2):

Using nose

The nose testing framework supports this.

Example (the code below is the entire contents of the file containing the test):

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

The output of the nosetests command:

> 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

This can be solved elegantly using 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()
Ответ 4

As of Python 3.4, subtests have been introduced to unittest for this purpose. See the documentation for details. TestCase.subTest is a context manager which allows one to isolate asserts in a test so that a failure will be reported with parameter information, but it does not stop the test execution. Here's the example from the documentation:

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)

The output of a test run would be:

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

This is also part of unittest2, so it is available for earlier versions of Python.

2024-01-27 08:16 python