pruebas - test en python
Prueba de unidad Python: ¿Generar pruebas múltiples programáticamente? (7)
nariz (sugerida por @Paul Hankin )
#!/usr/bin/env python
# file: test_pairs_nose.py
from nose.tools import eq_ as eq
from mymodule import f
def test_pairs():
for input, output in [ (2, 332), (234, 99213), (9, 3), ]:
yield _test_f, input, output
def _test_f(input, output):
try:
eq(f(input), output)
except AssertionError:
if input == 9: # expected failure
from nose.exc import SkipTest
raise SkipTest("expected failure")
else:
raise
if __name__=="__main__":
import nose; nose.main()
Ejemplo:
$ nosetests test_pairs_nose -v
test_pairs_nose.test_pairs(2, 332) ... ok
test_pairs_nose.test_pairs(234, 99213) ... ok
test_pairs_nose.test_pairs(9, 3) ... SKIP: expected failure
----------------------------------------------------------------------
Ran 3 tests in 0.001s
OK (SKIP=1)
unittest (enfoque similar al de @ doublep )
#!/usr/bin/env python
import unittest2 as unittest
from mymodule import f
def add_tests(generator):
def class_decorator(cls):
"""Add tests to `cls` generated by `generator()`."""
for f, input, output in generator():
test = lambda self, i=input, o=output, f=f: f(self, i, o)
test.__name__ = "test_%s(%r, %r)" % (f.__name__, input, output)
setattr(cls, test.__name__, test)
return cls
return class_decorator
def _test_pairs():
def t(self, input, output):
self.assertEqual(f(input), output)
for input, output in [ (2, 332), (234, 99213), (9, 3), ]:
tt = t if input != 9 else unittest.expectedFailure(t)
yield tt, input, output
class TestCase(unittest.TestCase):
pass
TestCase = add_tests(_test_pairs)(TestCase)
if __name__=="__main__":
unittest.main()
Ejemplo:
$ python test_pairs_unit2.py -v
test_t(2, 332) (__main__.TestCase) ... ok
test_t(234, 99213) (__main__.TestCase) ... ok
test_t(9, 3) (__main__.TestCase) ... expected failure
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK (expected failures=1)
Si no desea instalar unittest2
, agregue:
try:
import unittest2 as unittest
except ImportError:
import unittest
if not hasattr(unittest, ''expectedFailure''):
import functools
def _expectedFailure(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
func(*args, **kwargs)
except AssertionError:
pass
else:
raise AssertionError("UnexpectedSuccess")
return wrapper
unittest.expectedFailure = _expectedFailure
Posible duplicado:
¿Cómo generar pruebas de unidades dinámicas (parametrizadas) en python?
Tengo una función para probar, under_test
y un conjunto de pares de entrada / salida esperados:
[
(2, 332),
(234, 99213),
(9, 3),
# ...
]
Me gustaría que cada uno de estos pares de entrada / salida se pruebe en su propio método test_*
. ¿Es eso posible?
Esto es algo así como lo que quiero, pero forzando cada par de entrada / salida en una sola prueba:
class TestPreReqs(unittest.TestCase):
def setUp(self):
self.expected_pairs = [(23, 55), (4, 32)]
def test_expected(self):
for exp in self.expected_pairs:
self.assertEqual(under_test(exp[0]), exp[1])
if __name__ == ''__main__'':
unittest.main()
(Además, ¿realmente quiero poner esa definición de self.expected_pairs
en setUp
?)
ACTUALIZACIÓN: Probar el consejo de DoubleP :
class TestPreReqs(unittest.TestCase):
def setUp(self):
expected_pairs = [
(2, 3),
(42, 11),
(3, None),
(31, 99),
]
for k, pair in expected_pairs:
setattr(TestPreReqs, ''test_expected_%d'' % k, create_test(pair))
def create_test (pair):
def do_test_expected(self):
self.assertEqual(get_pre_reqs(pair[0]), pair[1])
return do_test_expected
if __name__ == ''__main__'':
unittest.main()
Esto no funciona. 0 pruebas se ejecutan. ¿He adaptado el ejemplo incorrectamente?
Algunas de las herramientas disponibles para realizar pruebas parametrizadas en Python son:
- Generadores de prueba de nariz (solo para pruebas funcionales, no para clases TestCase)
- nose-parametrized en la nose-parametrized por David Wolever (también para las clases de TestCase)
- Plantilla Unittest de Boris Feld
- Pruebas parametrizadas en py.test
- parametrized-testcase de parametrized-testcase por Austin Bingham
Vea también la pregunta 1676269 para obtener más respuestas a esta pregunta.
Como ocurre a menudo con Python, existe una forma complicada de proporcionar una solución simple.
En ese caso, podemos usar metaprogramación, decoradores y varios ingeniosos trucos de Python para lograr un buen resultado. Así es como se verá la prueba final:
import unittest
# some magic code will be added here later
class DummyTest(unittest.TestCase):
@for_examples(1, 2)
@for_examples(3, 4)
def test_is_smaller_than_four(self, value):
self.assertTrue(value < 4)
@for_examples((1,2),(2,4),(3,7))
def test_double_of_X_is_Y(self, x, y):
self.assertEqual(2 * x, y)
if __name__ == "__main__":
unittest.main()
Al ejecutar este script, el resultado es:
..F...F
======================================================================
FAIL: test_double_of_X_is_Y(3,7)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/xdecoret/Documents/foo.py", line 22, in method_for_example
method(self, *example)
File "/Users/xdecoret/Documents/foo.py", line 41, in test_double_of_X_is_Y
self.assertEqual(2 * x, y)
AssertionError: 6 != 7
======================================================================
FAIL: test_is_smaller_than_four(4)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/xdecoret/Documents/foo.py", line 22, in method_for_example
method(self, *example)
File "/Users/xdecoret/Documents/foo.py", line 37, in test_is_smaller_than_four
self.assertTrue(value < 4)
AssertionError
----------------------------------------------------------------------
Ran 7 tests in 0.001s
FAILED (failures=2)
que logra nuestro objetivo:
- es discreto: derivamos de TestCase como de costumbre
- escribimos pruebas parametrizadas solo una vez
- cada valor de ejemplo se considera una prueba individual
- el decorador se puede apilar, por lo que es fácil usar conjuntos de ejemplos (por ejemplo, usar una función para compilar la lista de valores de archivos de ejemplo o directorios)
- guinda del pastel, funciona por ariedad arbitraria de la firma
Entonces, cómo funciona. Básicamente, el decorador almacena los ejemplos en un atributo de la función. Usamos metaclass para reemplazar cada función decorada con una lista de funciones. Y reemplazamos el unittest.TestCase con nuestro nuevo El código mágico (que se pegará en el comentario "mágico" anterior) es:
__examples__ = "__examples__"
def for_examples(*examples):
def decorator(f, examples=examples):
setattr(f, __examples__, getattr(f, __examples__,()) + examples)
return f
return decorator
class TestCaseWithExamplesMetaclass(type):
def __new__(meta, name, bases, dict):
def tuplify(x):
if not isinstance(x, tuple):
return (x,)
return x
for methodname, method in dict.items():
if hasattr(method, __examples__):
dict.pop(methodname)
examples = getattr(method, __examples__)
delattr(method, __examples__)
for example in (tuplify(x) for x in examples):
def method_for_example(self, method = method, example = example):
method(self, *example)
methodname_for_example = methodname + "(" + ", ".join(str(v) for v in example) + ")"
dict[methodname_for_example] = method_for_example
return type.__new__(meta, name, bases, dict)
class TestCaseWithExamples(unittest.TestCase):
__metaclass__ = TestCaseWithExamplesMetaclass
pass
unittest.TestCase = TestCaseWithExamples
Si alguien quiere empaquetarlo bien, o proponer un parche para la prueba de unidad, ¡siéntase libre! Una cita de mi nombre será apreciada.
- Editar --------
El código puede simplificarse mucho y encapsularse por completo en el decorador si está listo para usar la introspección de cuadros (importar el módulo sys)
def for_examples(*parameters):
def tuplify(x):
if not isinstance(x, tuple):
return (x,)
return x
def decorator(method, parameters=parameters):
for parameter in (tuplify(x) for x in parameters):
def method_for_parameter(self, method=method, parameter=parameter):
method(self, *parameter)
args_for_parameter = ",".join(repr(v) for v in parameter)
name_for_parameter = method.__name__ + "(" + args_for_parameter + ")"
frame = sys._getframe(1) # pylint: disable-msg=W0212
frame.f_locals[name_for_parameter] = method_for_parameter
return None
return decorator
Con las pruebas de nariz, entonces sí. Ver esto: https://nose.readthedocs.org/en/latest/writing_tests.html#test-generators
Creo que la solución de Rory es la más limpia y la más corta. Sin embargo, esta variación de "crear funciones sintéticas en un TestCase" de DoubleP también funciona:
from functools import partial
class TestAllReports(unittest.TestCase):
pass
def test_spamreport(name):
assert classify(getSample(name))==''spamreport'', name
for rep in REPORTS:
testname = ''test_''+rep
testfunc = partial(test_spamreport, rep)
testfunc.__doc__ = testname
setattr( TestAllReports, testname, testfunc )
if __name__==''__main__'':
unittest.main(argv=sys.argv + [''--verbose''])
No probado:
class TestPreReqs(unittest.TestCase):
...
def create_test (pair):
def do_test_expected(self):
self.assertEqual(under_test(pair[0]), pair[1])
return do_test_expected
for k, pair in enumerate ([(23, 55), (4, 32)]):
test_method = create_test (pair)
test_method.__name__ = ''test_expected_%d'' % k
setattr (TestPreReqs, test_method.__name__, test_method)
Si usa esto a menudo, podría embellecer esto usando funciones de utilidad y / o decoradores, supongo. Tenga en cuenta que los pares no son un atributo del objeto TestPreReqs
en este ejemplo (y por eso setUp
se ha ido). Por el contrario, están "cableados" en cierto sentido en la clase TestPreReqs
.
Tenía que hacer algo similar. TestCase
subclases simples de TestCase
que tomaron un valor en su __init__
, así:
class KnownGood(unittest.TestCase):
def __init__(self, input, output):
super(KnownGood, self).__init__()
self.input = input
self.output = output
def runTest(self):
self.assertEqual(function_to_test(self.input), self.output)
Luego hice un banco de pruebas con estos valores:
def suite():
suite = unittest.TestSuite()
suite.addTests(KnownGood(input, output) for input, output in known_values)
return suite
Luego puede ejecutarlo desde su método principal:
if __name__ == ''__main__'':
unittest.TextTestRunner().run(suite())
Las ventajas de esto son:
- A medida que agrega más valores, la cantidad de pruebas informadas aumenta, lo que le hace sentir que está haciendo más.
- Cada caso de prueba individual puede fallar individualmente
- Es conceptualmente simple, ya que cada valor de entrada / salida se convierte en un TestCase