2008-11-24 25 views
9

Resumen Ejecutivo: Cuando los errores de aserción se tiran en los hilos, la prueba de la unidad no muere. Esto tiene sentido, ya que no se debe permitir que un subproceso bloquee otro subproceso. La pregunta es ¿cómo puedo 1) hacer que toda la prueba falle cuando el primero de los subprocesos auxiliares falla o 2) recorrer y determinar el estado de cada subproceso después de que se hayan completado (ver código a continuación). Una forma de hacer esto último es tener una variable de estado por subproceso, por ejemplo, "boolean [] estados" y tener "estados [i] == falso" significa que el subproceso falló (esto podría ampliarse para capturar más información). Sin embargo, eso no es lo que quiero: quiero que falle como cualquier otra prueba unitaria cuando se lanzan los errores de afirmación. ¿Esto es posible? ¿Es deseable?¿Cómo realizo una prueba unitaria usando hilos?

Me aburrí y decidí engendrar un montón de hilos en mi prueba de unidad y luego hacer que llamaran a un método de servicio, por el gusto de hacerlo. El código es aproximadamente como:

Thread[] threads = new Thread[MAX_THREADS]; 
for(int i = 0; i < threads.length; i++) { 
    threads[i] = new Thread(new Runnable() { 
     private final int ID = threadIdSequenceNumber++; 
     public void run() { 
      try { 
       resultRefs[ID] = runTest(Integer.toString(ID)); // returns an object 
      } 
      catch(Throwable t) { 
       // this code is EVIL - it catches even 
       // Errors - don't copy it - more on this below 
       final String message = "error testing thread with id => " 
          + ID; 
       logger.debug(message, t); 
       throw new IllegalStateException(message, t); 
       // need to wrap throwable in a 
       // run time exception so it will compile 
      } 
     } 
    }); 
} 

Después de esto, vamos a recorrer la matriz de hilos e iniciar cada uno. Después de eso, esperaremos a que todos terminen. Finalmente, realizaremos algunos controles en las referencias de resultados.

for(Thread thread : threads) 
    thread.start(); 

logger.debug("waiting for threads to finish ..."); 
boolean done = false; 
while(!done) { 
    done = true; 
    for(Thread thread : threads) 
     if(thread.isAlive()) 
      done = false; 
} 

for(int i = 0; i < resultRefs.length; i++) { 
    assertTrue("you've got the world messed, dawg!", 
      myCondition(resultRefs[i])); 

Aquí está el problema. ¿Has notado ese desagradable bloqueo try-catch-throwable? Acabo de agregar eso como un hack temporal para poder ver lo que estaba pasando. En runTest (String) se hacen algunas afirmaciones, por ejemplo, assertNotNull (null), pero dado que está en un hilo diferente, ¡¡no hace que la prueba de unidad falle !!!!

Mi conjetura es que tendremos que recorrer alguna manera sobre la matriz hilos, verificar el estado de cada uno, y de forma manual causar un error de aserción si el hilo terminado de una manera desagradable. ¿Cuál es el nombre del método que proporciona esta información (el seguimiento de la pila del hilo muerto)?

Respuesta

8

concurrencia es una de esas cosas que son muy difíciles de probar la unidad. Si solo está tratando de probar que el código dentro de cada hilo está haciendo lo que se supone que debe probar, puede ser que solo deba probar este código aislado del contexto. Si en este ejemplo los subprocesos colaboran para alcanzar un resultado, puede probar esa colaboración sin utilizar subprocesos. Eso se haría ejecutando todas las partes colaborativas secuencialmente. Si desea probar las condiciones de carrera y este tipo de cosas, las pruebas unitarias no son la mejor manera. Obtendrás pruebas que algunas veces fallan y algunas veces no fallan. Para resumir, creo que su problema puede ser que usted está realizando pruebas unitarias en un nivel demasiado alto. espero que esto ayude

+0

buen punto. Yo era principalmente interesante al escribir la prueba por curiosidad. Veo que no es una cosa trivial de hacer. :( – les2

+0

Es interesante que la programación esté empujando hacia más multihilo y más pruebas unitarias al mismo tiempo. Los dos parecen objetivos incompatibles, a menos que avancemos hacia lenguajes funcionales puros donde los problemas de simultaneidad disminuyen ... –

4

El Blog de exámenes de Google tenían un excelente artículo sobre este tema que vale la pena leer: http://googletesting.blogspot.com/2008/08/tott-sleeping-synchronization.html

Está escrito en Python, pero creo que los principios son directamente transferibles a Java.

+0

¡Impresionante! Mi primer encuentro con python threading.Event. Debería resolver mi problema con la unidad de prueba de código de subprocesos múltiples. – swdev

2

Las pruebas unitarias en un entorno multiproceso son difíciles ... por lo que es necesario realizar algunos ajustes. Las pruebas unitarias deben ser repetibles ... deterministas. Como resultado, cualquier cosa con múltiples hilos falla este criterio. Las pruebas con múltiples hilos también tienden a ser lentas.

  • Yo trataría de ver si puedo pasar con las pruebas en un solo hilo ... ¿la lógica bajo prueba realmente necesita múltiples hilos.
  • Si eso no funciona, vaya con el enfoque de variable de miembro que puede verificar contra un valor esperado al final de la prueba, cuando todos los subprocesos han terminado de ejecutarse.

Parece que hay otra pregunta como esta. Revise mi publicación para obtener un enlace a una discusión más larga en el tdd yahoogroup Unit testing a multithreaded application?

1

Su envoltura ejecutable debe pasar el objeto de excepción a su clase de prueba y luego puede almacenarlos en una colección. Cuando todas las pruebas hayan finalizado, puedes probar la colección. Si no está vacío, itere sobre cada una de las excepciones y .printStackTrace() y luego falle.

0

Otra opción popular para las pruebas de hilos concurrentes de Junit es el método de Matthieu Carbou utilizando un JunitRunner personalizado y una anotación simple.

See the full documentation

0

Es posible hacer la prueba de la unidad a fallar, mediante el uso de un objeto de sincronización especial. Eche un vistazo al siguiente artículo: Sprinkler - Advanced synchronization object

Trataré de explicar los puntos principales aquí. Desea poder externalizar fallas de subprocesos internos al subproceso principal, que, en su caso, es la prueba. Por lo tanto, debe usar un objeto/bloqueo compartido que usará tanto el subproceso interno como la prueba para sincronizarse entre sí. Consulte la siguiente prueba: crea un hilo que simula una excepción lanzada llamando a un objeto compartido llamado Sprinkler. El hilo principal (la prueba) está bloqueado en Sprinkler.getInstance(). Await (CONTEXT, 10000) que, en el momento en que se lanza la versión, será libre y atrapará la excepción lanzada. En el bloque catch, puede escribir la afirmación que no pasa la prueba.

@Test 
    public void testAwait_InnerThreadExternalizeException() { 

     final int CONTEXT = 1; 
     final String EXCEPTION_MESSAGE = "test inner thread exception message"; 

     // release will occur sometime in the future - simulate exception in the releaser thread 
     ExecutorServiceFactory.getCachedThreadPoolExecutor().submit(new Callable<void>() { 

      @Override 
      public Void call() throws Exception { 

       Sprinkler.getInstance().release(CONTEXT, new RuntimeException(EXCEPTION_MESSAGE)); 

       return null; 
      } 

     }); 

     Throwable thrown = null; 
     try { 
      Sprinkler.getInstance().await(CONTEXT, 10000); 
     } catch (Throwable t) { 
      // if the releaser thread delivers exception it will be externelized to this thread 
      thrown = t; 
     } 
     Assert.assertTrue(thrown instanceof SprinklerException); 
     Assert.assertEquals(EXCEPTION_MESSAGE, thrown.getCause().getMessage()); 
    } 
Cuestiones relacionadas