unittest test pruebas integracion español python tdd unit-testing

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?



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



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