2010-04-07 17 views
17

¿Qué estoy haciendo mal al lanzar una excepción en lugar de mostrar una falla, o no debería tener aserciones dentro de los hilos?junit assert en thread throws excepción

@Test 
public void testComplex() throws InterruptedException { 
    int loops = 10; 
    for (int i = 0; i < loops; i++) { 
    final int j = i; 
    new Thread() { 
    @Override 
    public void run() { 
    ApiProxy.setEnvironmentForCurrentThread(env);//ignore this 
    new CounterFactory().getCounter("test").increment();//ignore this too 
    int count2 = new CounterFactory().getCounter("test").getCount();//ignore 
    assertEquals(j, count2);//here be exceptions thrown. this is line 75 
    } 
    }.start(); 
    } 
    Thread.sleep(5 * 1000); 
    assertEquals(loops, new CounterFactory().getCounter("test").getCount()); 
} 

StackTrace

Exception in thread "Thread-26" junit.framework.AssertionFailedError: expected:<5> but was:<6> 
    at junit.framework.Assert.fail(Assert.java:47) 
    at junit.framework.Assert.failNotEquals(Assert.java:277) 
    at junit.framework.Assert.assertEquals(Assert.java:64) 
    at junit.framework.Assert.assertEquals(Assert.java:195) 
    at junit.framework.Assert.assertEquals(Assert.java:201) 
    at com.bitdual.server.dao.ShardedCounterTest$3.run(ShardedCounterTest.java:77) 
+0

¿Cuál es su StackTrace? –

+0

@Frederik agregó stacktrace –

+0

¿Por qué está creando un nuevo hilo en esta prueba? Quiero decir, ¿por qué h @ $! ¿Te gustaría crear hilos en una prueba de unidad? –

Respuesta

30

El marco JUnit captura sólo errores aserción en el hilo principal de ejecutar la prueba. No tiene conocimiento de excepciones desde dentro de nuevos hilos de generación. Para hacerlo bien, debe comunicar el estado de terminación del subproceso al subproceso principal. Debe sincronizar los subprocesos correctamente y usar algún tipo de variable compartida para indicar el resultado del subproceso anidado.

EDIT:

Aquí es una solución genérica que puede ayudar:

class AsynchTester{ 
    private Thread thread; 
    private volatile AssertionError exc; 

    public AsynchTester(final Runnable runnable){ 
     thread = new Thread(new Runnable(){ 
      public void run(){ 
       try{    
        runnable.run(); 
       }catch(AssertionError e){ 
        exc = e; 
       } 
      } 
     }); 
    } 

    public void start(){ 
     thread.start(); 
    } 

    public void test() throws InterruptedException{ 
     thread.join(); 
     if (exc != null) 
      throw exc; 
    } 
} 

Usted debe darlo el ejecutable en el constructor, y luego sólo tiene que llamar a start() para activar y prueba () validar. El método de prueba esperará si es necesario y arrojará el error de aserción en el contexto del subproceso principal.

+1

* "Debe sincronizar los hilos correctamente ..." * en este ejemplo, la manera simple es que el hilo principal llame a 'join()' en el hilo secundario ... y se deshaga del 'sleep (5000) 'llamada. –

+0

La llamada de suspensión olía un poco, pero no insistí en ella ya que era el código de prueba de la unidad, pero sin duda usaré la forma correcta ahora que lo sé. –

4

Donde hay múltiples hilos de trabajo, como en la pregunta original, simplemente unir uno de ellos no es suficiente. Idealmente, querrá esperar a que se completen todos los subprocesos de trabajo mientras sigue informando los errores de aserción en el hilo principal, como en la respuesta de Eyal.

Aquí está un ejemplo sencillo de cómo hacer esto utilizando ConcurrentUnit:

public class MyTest extends ConcurrentTestCase { 
    @Test 
    public void testComplex() throws Throwable { 
     int loops = 10; 
     for (int i = 0; i < loops; i++) { 
      new Thread(new Runnable() { 
       public void run() { 
        threadAssertEquals(1, 1); 
        resume(); 
       } 
      }).start(); 
     } 

     threadWait(100, loops); // Wait for 10 resume calls 
    } 
} 
0

Terminé usando este patrón que funcione tanto con Runnables y Temas. Se inspira en gran medida de la respuesta de @Eyal Schneider:

private final class ThreadUnderTestWrapper extends ThreadUnderTest { 
    private Exception ex; 

    @Override 
    public void run() { 
     try { 
      super.run(); 
     } catch (Exception ex) { 
      this.ex = ex; 
     } 
    } 

    public Exception getException() throws InterruptedException { 
     super.join(); // use runner.join here if you use a runnable. 
     return ex; 
    } 
} 
2

Una pequeña mejora a Eyal Schneider's answer:
El ExecutorService permite presentar una Callable y excepciones emitidas o errores se relanza por el Future devuelto.
En consecuencia, la prueba se puede escribir como:

@Test 
public void test() throws Exception { 
    ExecutorService es = Executors.newSingleThreadExecutor(); 
    Future<?> future = es.submit(() -> { 
    testSomethingThatMightThrowAssertionErrors(); 
    return null; 
    }); 

    future.get(); // This will rethrow Exceptions and Errors as ExecutionException 
} 
Cuestiones relacionadas