Pruebas repetidas individuales o múltiples con nariz

De forma similar a esta pregunta , me gustaría que Nose realice una prueba (o todas las pruebas) n veces, pero no en paralelo.

Tengo unos cientos de pruebas en un proyecto; Algunas son algunas pruebas unitarias simples. Otras son pruebas de integración con cierto grado de concurrencia. Con frecuencia, al depurar las pruebas quiero “golpear” una prueba con más fuerza; un bucle de bash funciona, pero permite una gran cantidad de resultados desordenados, no más “. para cada prueba que pasa Tener la capacidad de vencer en las pruebas seleccionadas para una serie de pruebas parece algo natural para pedirle a Nose que lo haga, pero no lo he encontrado en ningún lugar en la documentación.

¿Cuál es la forma más sencilla de hacer que Nose haga esto (que no sea un bucle de bash)?

Puede escribir una prueba de la nariz como un generador , y la nariz ejecutará cada función producida:

 def check_something(arg): # some test ... def test_something(): for arg in some_sequence: yield (check_something, arg) 

Usando nose-testconfig , podría hacer que el número de prueba ejecute un argumento de línea de comando:

 from testconfig import config # ... def test_something(): for n in range(int(config.get("runs", 1))): yield (check_something, arg) 

Que llamarías desde la línea de comandos con, por ejemplo,

 $ nosetests --tc=runs:5 

… por más de una carrera.

Alternativamente (pero también usando nose-testconfig), podrías escribir un decorador:

 from functools import wraps from testconfig import config def multi(fn): @wraps(fn) def wrapper(): for n in range(int(config.get("runs", 1))): fn() return wrapper @multi def test_something(): # some test ... 

Y luego, si desea dividir sus pruebas en diferentes grupos, cada uno con su propio argumento de línea de comando para el número de ejecuciones:

 from functools import wraps from testconfig import config def multi(cmd_line_arg): def wrap(fn): @wraps(fn) def wrapper(): for n in range(int(config.get(cmd_line_arg, 1))): fn() return wrapper return wrap @multi("foo") def test_something(): # some test ... @multi("bar") def test_something_else(): # some test ... 

Que puedes llamar así:

 $ nosetests --tc=foo:3 --tc=bar:7 

Una forma es en la propia prueba:

Cambia esto:

 class MyTest(unittest.TestCase): def test_once(self): ... 

A esto:

 class MyTest(unittest.TestCase): def assert_once(self): ... def test_many(self): for _ in range(5): self.assert_once() 

Tendrá que escribir un script para hacer esto, pero puede repetir los nombres de prueba en la línea de comandos X veces.

 nosetests testname testname testname testname testname testname testname 

etc.

La solución que terminé usando es create sh script run_test.sh:

 var=0 while $1; do ((var++)) echo "*** RETRY $var" done 

Uso:

 ./run_test.sh "nosetests TestName" 

Se ejecuta la prueba infinitamente, pero se detiene en el primer error.

Nunca debe haber una razón para realizar una prueba más de una vez. Es importante que sus pruebas sean deterministas (es decir, dado el mismo estado de la base de código, siempre producen el mismo resultado). Si este no es el caso, entonces, en lugar de ejecutar pruebas más de una vez, debe volver a diseñar las pruebas y / o Código para que sean.

Por ejemplo, una razón por la que las pruebas fallan de manera intermitente es una condición de carrera entre la prueba y el código en prueba (CUT). En esta circunstancia, una respuesta ingenua es agregar un gran “sueño vudú” a la prueba, para “asegurarse” de que la CUT haya terminado antes de que la prueba comience a afirmarse.

Sin embargo, esto es propenso a errores, porque si su CUT es lento por cualquier motivo (hardware con poca potencia, caja cargada, base de datos ocupada, etc.), entonces fallará esporádicamente. Una solución mejor en este caso es hacer que su prueba espere un evento , en lugar de dormir.

El evento podría ser cualquier cosa de su elección. A veces, los eventos que puede usar ya se están generando (por ejemplo, los eventos de DOM de Javascript, el tipo de eventos ‘pageRendered’ que las pruebas de Selenium pueden usar.) Otras veces, puede ser apropiado que agregue un código a su CUT, lo que genera un evento cuando se realiza (tal vez su architecture incluya otros componentes que estén interesados ​​en eventos como este).

A menudo, sin embargo, deberá volver a escribir la prueba para que intente detectar si su CUT ha terminado de ejecutarse (p. Ej., ¿Ya existe el archivo de salida?), Y si no, duerme 50 ms y luego vuelve a intentarlo. Eventualmente se agotará y fallará, pero solo lo hará después de un tiempo muy largo (por ejemplo, 100 veces el tiempo de ejecución esperado de su CUT)

Otro enfoque es diseñar su CUT utilizando los principios de ‘cebolla / hexagonal / puertos’ n ‘adaptadores’, lo que insiste en que su lógica empresarial debe estar libre de todas las dependencias externas. Esto significa que su lógica de negocios se puede probar utilizando pruebas unitarias simples de menos de milisegundos, que nunca tocan la red o el sistema de archivos. Una vez hecho esto, necesita muchas menos pruebas de sistema de extremo a extremo, ya que ahora se están realizando solo como pruebas de integración, y no es necesario que intente manipular cada detalle y borde de la lógica de su negocio a través de la interfaz de usuario. . Este enfoque también producirá grandes beneficios en otras áreas, como el diseño mejorado de CUT (que reduce las dependencias entre componentes), las pruebas son mucho más fáciles de escribir y el tiempo necesario para ejecutar todo el conjunto de pruebas se reduce mucho.

El uso de métodos como el anterior puede eliminar por completo el problema de las pruebas no confiables, y recomendaría hacerlo, para mejorar no solo sus pruebas, sino también su base de código y sus capacidades de diseño.