2010-02-03 12 views
6

Necesito crear una prueba unitaria para alguna clase de python. Tengo una base de datos de las entradas y los resultados esperados que debe generar el UUT para esas entradas.Cómo escribir una prueba unitaria donde cada caso de prueba tiene una entrada diferente pero hace lo mismo?

Aquí es el pseudo-código de lo que quiero hacer:

for i=1 to NUM_TEST_CASES: 
    Load input for test case i 
    execute UUT on the input and save output of run 
    Load expected result for test case i 
    Compare output of run with the expected result 

puedo lograr esto usando el paquete unittest o hay algún paquete mejor prueba para este fin?

+0

Para aclarar su pregunta, ¿se refiere a algo así como a los proveedores de datos en PHPUnit? http://www.phpunit.de/manual/current/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers – majelbstoat

Respuesta

4

La manera de describir las pruebas es una coincidencia extraña para las pruebas unitarias en general. Por lo general, las pruebas unitarias no cargan datos de prueba o descansan resultados de archivos externos. En general, simplemente está codificado en la prueba unitaria.

Eso no quiere decir que su plan no funcionará. Es solo decir que es atípico.

Tiene dos opciones.

  1. (Qué hacemos). Escriba un pequeño script que haga "Cargar entrada para el caso de prueba i" y "Cargar el resultado esperado para el caso de prueba i". Use esto para generar el código unittest requerido. (Utilizamos las plantillas Jinja2 para escribir el código Python a partir de los archivos de origen.)

    A continuación, elimine los archivos de origen. Sí, eliminarlos. Ellos solo te confundirán.

    Lo que queda son archivos Unittest correctos en el formulario "típico" con datos estáticos para el caso de prueba y los resultados esperados.

  2. Escriba su método setUp para hacer la "Entrada de carga para el caso de prueba i" y "Cargar el resultado esperado para el caso de prueba i". Escriba su método test para ejercitar el UUT.

Podría ser así.

class OurTest(unittest.TestCase): 
    def setUp(self): 
     self.load_data() 
     self.load_results() 
     self.uut = ... UUT ... 
    def runTest(self): 
     ... exercise UUT with source data ... 
     ... check results, using self.assertXXX methods ... 

¿Desea ejecutar esto muchas veces? Una forma de hacer algo como esto.

class Test1(OurTest): 
    source_file = 'this' 
    result_file = 'that' 

class Test2(OutTest): 
    source_file= 'foo' 
    result_file= 'bar' 

Esto permitirá que el programa principal unittest encuentre y ejecute sus pruebas.

+0

Gracias. Creo que iré por la opción 1. Jinja2 parece interesante. ¿Qué licencia usan? –

+1

@zr: "¿Qué licencia usan?" ¿Por qué me preguntas? La página de PyPi dice esto: http://pypi.python.org/pypi/Jinja2. –

0

Quizás podría usar doctest para esto. Conocer sus entradas y salidas (y ser capaz de asignar el número de caso de un nombre de función) que debe ser capaz de producir un archivo de texto como este:

>>> from XXX import function_name1 
>>> function_name1(input1) 
output1 
>>> from XXX import function_name2 
>>> function_name2(input2) 
output2 
... 

Y a continuación, sólo tiene que utilizar doctest.testfile('cases.txt'). Podría valer la pena intentarlo.

3

Hacemos algo como esto con el fin de ejecutar lo que son en realidad integración (regresión) pruebas dentro de la unittest marco (en realidad una personalización de la casa de la misma que nos da enormes beneficios como la ejecución de las pruebas en paralelo en un clúster de máquinas, etc., etc. - el gran valor agregado de esa personalización es la razón por la cual estamos tan interesados ​​en utilizar el marco unittest).

Cada prueba se representa en un archivo (los parámetros a usar en esa prueba, seguidos de los resultados esperados).Nuestra integration_test lee todos esos archivos de un directorio, analiza cada uno de ellos, y luego llama:

def addtestmethod(testcase, uut, testname, parameters, expresults): 
    def testmethod(self): 
    results = uut(parameters) 
    self.assertEqual(expresults, results) 
    testmethod.__name__ = testname 
    setattr(testcase, testname, testmethod) 

Empezamos con una clase de caso de prueba vacío:

class IntegrationTest(unittest.TestCase): pass 

y luego llamar a addtestmethod(IntegrationTest, ... en un bucle en que estamos leyendo todos los archivos relevantes y analizándolos para obtener el nombre de prueba, los parámetros y las expresiones.

Finalmente, llamamos a nuestro propio corredor de prueba especializada que hace el trabajo pesado (distribución de las pruebas sobre las máquinas disponibles en un clúster, la recogida de resultados, etc.). No queríamos reinventar esa rueda de alto valor agregado, así que estamos haciendo un caso de prueba tan cercano al típico "codificado a mano" como se necesita para "engañar" al corredor de pruebas para que funcione correctamente para nosotros;)

A menos que tenga razones específicas (buenos corredores de prueba o similares) para usar el enfoque unittest para sus pruebas (de integración), puede encontrar que su vida es más simple con un enfoque diferente. Sin embargo, este es bastante viable y estamos muy contentos con sus resultados (que en su mayoría incluyen ejecuciones increíblemente rápidas de grandes conjuntos de pruebas de integración/regresión).

+1

Me encuentro usando "prueba unitaria" para indicar "pruebas que escribo para ejecutar con unittest o nUnit", aunque a menudo no son pruebas unitarias en absoluto. –

0

También puede ser que desee echar un vistazo a my answer a this question. Una vez más, estoy tratando de hacer pruebas de regresión en lugar de pruebas unitarias per-se, pero el marco de prueba unitario es bueno para ambos.

En mi caso, he tenido unos archivos docena de entrada, que cubre una extensión razonable de los diferentes casos de uso, y que tenía funciones de prueba alrededor de media docena que quería hacer un llamamiento a cada uno.

En lugar de escribir 72 pruebas diferentes, la mayoría de las cuales eran idénticas aparte de los parámetros de entrada y datos de resultados, creé un diccionario de resultados (con la clave siendo los parámetros de entrada y el valor siendo un diccionario de resultados para cada función bajo prueba). Luego escribí una única clase TestCase para probar cada una de las 6 funciones y la repliqué sobre los 12 archivos de prueba agregando el TestCase al conjunto de pruebas varias veces.

1

A mí parece que pytest tiene justo lo que necesita.

Puede parametrise tests para que se ejecuten las mismas pruebas tantas veces como tenga entradas y todo lo que necesita es un decorador (no hay bucles, etc.).

Aquí está un ejemplo sencillo:

import pytest 
@pytest.mark.parametrize("test_input,expected", [ 
    ("3+5", 8), 
    ("2+4", 6), 
    ("6*9", 42), 
]) 
def test_eval(test_input, expected): 
    assert eval(test_input) == expected 

Aquí parametros toma dos argumentos - los nombres de los parámetros como una cadena, y los valores de esos parámetros como un iterable.

test_eval serán convocados una vez para cada elemento de la lista.

Cuestiones relacionadas