2011-04-14 15 views
5

Tengo una función python que, si pasa datos incorrectos, queda atrapada en un bucle infinito. Me gustaría escribir una prueba unitaria para confirmar que maneja los parámetros incorrectos con elegancia. El problema es, por supuesto, que si no detecta los parámetros incorrectos, no volverá.prueba unitaria de Python contra un bucle infinito potencial

¿Es aceptable usar hilos para escribir una prueba para este tipo de cosas?

import threading, unittest 
import mymodule 

class CallSuspectFunctionThread(threading.Thread): 
    def __init__(self, func, *args): 
     self.func = func 
     self.args = args 
     super(CallSuspectFunctionThread, self).__init__() 
    def run(self): 
     self.func(*self.args) 

class TestNoInfiniteLoop(unittest.TestCase): 
    def test_no_infinite_loop(self): 
     myobj = mymodule.MyObject() 
     bad_args = (-1,0) 
     test_thread = CallSuspectFunctionThread(mybj.func_to_test, *bad_args) 
     test_thread.daemon = True 
     test_thread.start() 
     # fail if func doesn't return after 8 seconds 
     for i in range(32): 
      test_thread.join(0.25) 
      if not test_thread.is_alive(): 
       return 
     self.fail("function doesn't return") 

El único problema que veo con esto es que si no pasa la prueba que estoy atascado con este hilo adicional, lo que podría consumir recursos de CPU y memoria, mientras que el resto de las pruebas se ejecutan. Por otro lado, he corregido el código y la probabilidad de regresión aquí es muy pequeña, así que no sé si importa si incluyo la prueba o no.

Respuesta

1

No es posible que pueda probar esto. ¿Qué sucede si el otro hilo es simplemente "lento" o peor "enclavado en vivo" (es decir, esperando un recurso compartido)? Cualquier suposición que hagas puede entrar en conflicto con la realidad.

No puede encogerse de hombros y decir "Siempre solía terminar en 1,5 segundos, pero ahora parece demorar un poco más. Intentaré 2 segundos y veré si pasa". Eso no es aceptable.

Tiene que probar la terminación de todos los bucles todo el tiempo. Todos los bucles

Cualquier cosa menos es simplemente un diseño que es tan pobre que nunca debería ver la luz del día.

Si no puede evitar un ciclo infinito, aún no ha terminado de diseñar.

No es algo que se pueda probar.

+0

Buenos puntos. Entonces esta es una limitación de las pruebas unitarias. Sin embargo, ¿cómo se ** demuestra ** la finalización? En mi caso, estoy tratando de determinar algunas propiedades simples de una función matemática. Los casos de borde parecen ser donde la curva descrita no se comporta bien (es decir, no converge/sin extremo local). Admito que podría estar tomando el enfoque perezoso; es más fácil escribir una prueba y seguir adelante, luego averiguar dónde se está atascando. –

+1

@Aryeh Leib Taurog: Esto no es una limitación de "Pruebas unitarias". Esta es una limitación de todas las pruebas. No puede probar la terminación. Debes probarlo. Si está haciendo análisis de funciones, tiene que hacer los cálculos para determinar qué restricciones definen la convergencia y rechazar todas las condiciones iniciales que no pueden converger. Lo siento, pero tienes que hacer los cálculos. –

+3

No todos los problemas tienen una solución cerrada. La determinación de un conjunto completo de restricciones en la entrada en este caso no es un enfoque realista. Lo que quise decir es simplemente usar un contador de bucle para detener el cálculo una vez que se ha ido demasiado lejos. Pero también no siempre es fácil saber hasta qué punto está demasiado lejos. –

1

Agregaría la prueba para probar el bucle infinito, ya que si se cuelga durante las pruebas, al menos ha detectado el error antes de la producción. Si la posibilidad de que ocurra es escasa (como dices), entonces no me preocuparía por una prueba complicada, pero probaría el código de todos modos.

0

Tener una prueba para esta condición es una buena idea. Cuando se prueba el código, puede estar más seguro de su calidad.

En cuanto al hilo que crea para probar la función, aquí están two ways to kill a thread in python. Dado que se encuentra dentro de una función que tiene el ciclo infinito, deberá usar el segundo método para salir del ciclo.

+0

Vi eso, pero parecía excesivo, perdón por el juego de palabras. Si continúo la prueba a pesar del consejo de S. Lott en contra de ella, probablemente usaré el decorador timeout, como sugirió Michał Šrajer. –

12

Puede agregar un decorador timeout. Es bueno separar la lógica de su caso de prueba de la implementación del mecanismo de tiempo de espera. Esto hará que su código sea más legible y más fácil de mantener.

Ver http://pypi.python.org/pypi/timeout .

+1

+1 por simplicidad.Me gusta el hecho de que la biblioteca utiliza SIGALRM, que parece más apropiado que los hilos. Noto que no parece funcionar bajo Windows. Sin embargo, esto no aborda directamente la cuestión, que es la conveniencia de tal prueba. –

+1

-1: No funcionará muy bien en general. Un día tiene una máquina más ocupada de lo normal y tarda más que el tiempo de espera y está depurando la carga de trabajo general del sistema, no su aplicación. No puedes hacer esto con una prueba. –

+4

@ S.Lott: Al usuario final no le importa por qué colgó el programa: debe manejar todos los casos de la forma más elegante posible, incluidos aquellos en los que no cuenta con los recursos adecuados. Simplemente es inaceptable que una aplicación con una GUI tome más de un segundo para proporcionar retroalimentación a las acciones del usuario, por ejemplo. No necesita establecer un tiempo de espera que esté cerca de cuánto demorará una función en regresar. Si una función normalmente tarda 0.01 segundos en regresar, está bien establecerla en 10 segundos si la función puede tomar tanto tiempo y considerarse aceptable. – ArtOfWarfare

0

Actualmente no tiene que atraparlo. Mientras esté en tus pruebas, seguramente notarás si sale mal.

+0

No si está ejecutando las pruebas de su unidad en modo apagado. –

0

Quizás en su caso pueda resolverlo como dice la respuesta aceptada. Bien por usted. Sin embargo, existen casos (para pruebas de integración y de humo, no pruebas unitarias) en los que no se puede resolver el caso "se cuelga para siempre". Para estos casos, me gusta la solución this timeout.