2009-11-04 6 views
26

Duplicar posible:
How to generate dynamic (parametrized) unit tests in python?Escribir un reutilizable (parametrizada) Método unittest.TestCase

estoy escribiendo ensayos utilizando el paquete unittest, y quiero evitar el código repetido . Voy a llevar a cabo una serie de pruebas que requieren un método muy similar, pero con un solo valor diferente cada vez. Un ejemplo simple e inútil sería:

class ExampleTestCase(unittest.TestCase): 

    def test_1(self): 
     self.assertEqual(self.somevalue, 1) 

    def test_2(self): 
     self.assertEqual(self.somevalue, 2) 

    def test_3(self): 
     self.assertEqual(self.somevalue, 3) 

    def test_4(self): 
     self.assertEqual(self.somevalue, 4) 

¿Hay una manera de escribir el ejemplo anterior, sin repetir todo el código cada vez, pero en lugar de escribir un método genérico, por ejemplo,

def test_n(self, n): 
     self.assertEqual(self.somevalue, n) 

y diciendo a unittest que pruebe esta prueba con diferentes entradas?

+0

¿Ha encontrado una manera de hacer esto? ¿O puede que hayas encontrado otra herramienta para esta tarea? Necesito tal comportamiento tampoco. – legesh

+0

http://thebongtraveller.blogspot.sg/2015/12/art-of-unittest-writing-auto-generation.html ¿Es esto lo mismo? –

Respuesta

0

Tal vez algo como:

def test_many(self): 
    for n in range(0,1000): 
     self.assertEqual(self.somevalue, n) 
+1

Esto no es lo que estoy buscando porque se detendrá tan pronto como falle una de las pruebas. Estoy buscando una solución en la que una falla de prueba no impida la ejecución de las otras. – astrofrog

+0

@Morgoth: ¿Por qué? ¿Por qué ejecutar más pruebas cuando sabes que has fracasado? –

+3

Porque no hay nada que decir, las otras pruebas también fallarán. Es importante saber si todas las pruebas fallan, o solo una o dos, ya que esto puede ayudar a diagnosticar el problema. Es bueno saber desde el principio exactamente cuántas fallas tienes, no quieres tener que arreglarlas una por una hasta que se detengan. – astrofrog

1

Creo que lo que quiere es "pruebas" parametrizados.

No creo módulo unittest apoya esta (por desgracia), pero si estuviera agregando esta característica se vería algo como esto:

# Will run the test for all combinations of parameters 
@RunTestWith(x=[0, 1, 2, 3], y=[-1, 0, 1]) 
def testMultiplication(self, x, y): 
    self.assertEqual(multiplication.multiply(x, y), x*y) 

Con el módulo unittest existente, un decorador simple como esto no será capaz de "reproducir" las pruebas varias veces, pero creo que esto es factible el uso de una combinación de un decorador y una metaclase (metaclase deben observar todas las pruebas '*' métodos y replicar (bajo diferentes nombres generados automáticamente) los que tiene un decorador aplicado).

3

Si realmente quiere tener multiplettest, entonces necesita múltiples métodos. La única forma de conseguirlo es a través de algún tipo de generación de código. Puede hacerlo a través de una metaclases, o ajustando la clase después de la definición, incluso (si usa Python 2.6) a través de un decorador de clases.

Aquí es una solución que busca el 'multitest' especial y los miembros '' multitest_values ​​y utiliza los para construir los métodos de ensayo sobre la marcha. No es elegante, pero lo hace más o menos lo que quiere:

import unittest 
import inspect 

class SomeValue(object): 
    def __eq__(self, other): 
     return other in [1, 3, 4] 

class ExampleTestCase(unittest.TestCase): 
    somevalue = SomeValue() 

    multitest_values = [1, 2, 3, 4] 
    def multitest(self, n): 
     self.assertEqual(self.somevalue, n) 

    multitest_gt_values = "ABCDEF" 
    def multitest_gt(self, c): 
     self.assertTrue(c > "B", c) 


def add_test_cases(cls): 
    values = {} 
    functions = {} 
    # Find all the 'multitest*' functions and 
    # matching list of test values. 
    for key, value in inspect.getmembers(cls): 
     if key.startswith("multitest"): 
      if key.endswith("_values"): 
       values[key[:-7]] = value 
      else: 
       functions[key] = value 

    # Put them together to make a list of new test functions. 
    # One test function for each value 
    for key in functions: 
     if key in values: 
      function = functions[key] 
      for i, value in enumerate(values[key]): 
       def test_function(self, function=function, value=value): 
        function(self, value) 
       name ="test%s_%d" % (key[9:], i+1) 
       test_function.__name__ = name 
       setattr(cls, name, test_function) 

add_test_cases(ExampleTestCase) 

if __name__ == "__main__": 
    unittest.main() 

Ésta es la salida de al ejecutarlo

% python stackoverflow.py 
.F..FF.... 
====================================================================== 
FAIL: test_2 (__main__.ExampleTestCase) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "stackoverflow.py", line 34, in test_function 
    function(self, value) 
    File "stackoverflow.py", line 13, in multitest 
    self.assertEqual(self.somevalue, n) 
AssertionError: <__main__.SomeValue object at 0xd9870> != 2 

====================================================================== 
FAIL: test_gt_1 (__main__.ExampleTestCase) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "stackoverflow.py", line 34, in test_function 
    function(self, value) 
    File "stackoverflow.py", line 17, in multitest_gt 
    self.assertTrue(c > "B", c) 
AssertionError: A 

====================================================================== 
FAIL: test_gt_2 (__main__.ExampleTestCase) 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    File "stackoverflow.py", line 34, in test_function 
    function(self, value) 
    File "stackoverflow.py", line 17, in multitest_gt 
    self.assertTrue(c > "B", c) 
AssertionError: B 

---------------------------------------------------------------------- 
Ran 10 tests in 0.001s 

FAILED (failures=3) 

Se puede ver de inmediato algunos de los problemas que se producen con la generación de código. ¿De dónde viene "test_gt_1"? Podría cambiar el nombre por el más largo "test_multitest_gt_1", pero ¿qué prueba es 1? Mejor aquí sería comenzar desde _0 en lugar de _1, y quizás en su caso sepa que los valores se pueden usar como nombre de función de Python.

No me gusta este enfoque. He trabajado en bases de código que autogeneraron los métodos de prueba (en un caso utilizando una metaclase) y descubrí que era mucho más difícil de entender que útil. Cuando una prueba fallaba, era difícil determinar el origen del caso de falla, y era difícil mantener el código de depuración para investigar el motivo de la falla.

(Las fallas de depuración en el ejemplo que escribí aquí no es tan difícil como el enfoque de metaclass específico con el que tuve que trabajar.)

0

escribir un solo método de prueba que realiza todas las pruebas y captura todos los resultados, escribir sus propios mensajes de diagnóstico en stderr, y no pasan la prueba si alguna de sus subpruebas fallan:

def test_with_multiple_parameters(self): 
    failed = False 
    for k in sorted(self.test_parameters.keys()): 
     if not self.my_test(self.test_parameters[k]): 
      print >> sys.stderr, "Test {0} failed.".format(k) 
      failed = True 
    self.assertFalse(failed)    

Nota que, por supuesto, el nombre my_test() no puede comenzar con test.

1

Un enfoque más orientado a datos podría ser más claro que el utilizado en answerAndrew Dalke 's:

"""Parametrized unit test. 

Builds a single TestCase class which tests if its 
    `somevalue` method is equal to the numbers 1 through 4. 

This is accomplished by 
    creating a list (`cases`) 
    of dictionaries which contain test specifications 
    and then feeding the list to a function which creates a test case class. 

When run, the output shows that three of the four cases fail, 
    as expected: 

>>> import sys 
>>> from unittest import TextTestRunner 
>>> run_tests(TextTestRunner(stream=sys.stdout, verbosity=9)) 
... # doctest: +ELLIPSIS 
Test if self.somevalue equals 4 ... FAIL 
Test if self.somevalue equals 1 ... FAIL 
Test if self.somevalue equals 3 ... FAIL 
Test if self.somevalue equals 2 ... ok 
<BLANKLINE> 
====================================================================== 
FAIL: Test if self.somevalue equals 4 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    ... 
AssertionError: 2 != 4 
<BLANKLINE> 
====================================================================== 
FAIL: Test if self.somevalue equals 1 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    ... 
AssertionError: 2 != 1 
<BLANKLINE> 
====================================================================== 
FAIL: Test if self.somevalue equals 3 
---------------------------------------------------------------------- 
Traceback (most recent call last): 
    ... 
AssertionError: 2 != 3 
<BLANKLINE> 
---------------------------------------------------------------------- 
Ran 4 tests in ...s 
<BLANKLINE> 
FAILED (failures=3) 
""" 

from unittest import TestCase, TestSuite, defaultTestLoader 

cases = [{'name': "somevalue_equals_one", 
      'doc': "Test if self.somevalue equals 1", 
      'value': 1}, 
     {'name': "somevalue_equals_two", 
      'doc': "Test if self.somevalue equals 2", 
      'value': 2}, 
     {'name': "somevalue_equals_three", 
      'doc': "Test if self.somevalue equals 3", 
      'value': 3}, 
     {'name': "somevalue_equals_four", 
      'doc': "Test if self.somevalue equals 4", 
      'value': 4}] 

class BaseTestCase(TestCase): 
    def setUp(self): 
     self.somevalue = 2 

def test_n(self, n): 
    self.assertEqual(self.somevalue, n) 

def make_parametrized_testcase(class_name, base_classes, test_method, cases): 
    def make_parametrized_test_method(name, value, doc=None): 
     def method(self): 
      return test_method(self, value) 
     method.__name__ = "test_" + name 
     method.__doc__ = doc 
     return (method.__name__, method) 

    test_methods = (make_parametrized_test_method(**case) for case in cases) 
    class_dict = dict(test_methods) 
    return type(class_name, base_classes, class_dict) 


TestCase = make_parametrized_testcase('TestOneThroughFour', 
             (BaseTestCase,), 
             test_n, 
             cases) 

def make_test_suite(): 
    load = defaultTestLoader.loadTestsFromTestCase 
    return TestSuite(load(TestCase)) 

def run_tests(runner): 
    runner.run(make_test_suite()) 

if __name__ == '__main__': 
    from unittest import TextTestRunner 
    run_tests(TextTestRunner(verbosity=9)) 

No estoy seguro de lo que el vudú está involucrado en la determinación del orden en el que se ejecutan las pruebas, pero el doctest pasa consistentemente para mí, al menos.

Para situaciones más complejas, es posible reemplazar el elemento values de los diccionarios cases por una tupla que contenga una lista de argumentos y un dict de argumentos de palabra clave. Aunque en ese momento básicamente estás codificando lisp en python.

11

Algunas de las herramientas disponibles para hacer pruebas parametrizados en Python son:

+0

[Casos de prueba paramétricos] (http://eli.thegreenplace.net/2011/08/02/python-unit-testing-parametrized-test-cases) de Eli Bendersky funcionó muy bien para mí. –

Cuestiones relacionadas