2010-04-07 143 views
17

我在做什麼錯了,拋出異常而不是顯示失敗,或者我不應該在線程內部斷言?junit在線程拋出異常

@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()); 
} 

堆棧跟蹤

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

什麼是您的堆棧跟蹤? – 2010-04-07 23:05:53

+0

@Frederik增加了stacktrace – 2010-04-07 23:08:19

+0

爲什麼你在這個測試中創建一個新的線程?我的意思是,爲什麼h @ $!你想在單元測試中創建線程嗎? – 2010-04-07 23:56:11

回答

30

JUnit框架捕捉運行測試在主線程中唯一的斷言錯誤。它不知道新的spawn線程中的異常。 爲了做到這一點,您應該將線程的終止狀態傳達給主線程。您應該正確同步線程,並使用某種共享變量來指示嵌套線程的結果。

編輯:

這裏是一個通用的解決方案,可以幫助:

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; 
    } 
} 

你應該通過它可運行在構造函數中,然後你只需調用start()來激活和測試( ) 驗證。如果需要,測試方法將等待,並將斷言錯誤置於主線程的上下文中。

+1

*「你應該正確地同步線程......」*在這個例子中,簡單的方法是讓主線程在子線程上調用join(),然後擺脫sleep(5000) '電話。 – 2010-04-07 23:39:55

+0

睡眠電話有點味道,但我沒有詳細說明,因爲它是單元測試代碼,但我現在肯定會使用我知道的正確方法。 – 2010-04-08 00:09:20

4

在涉及多個工作線程的情況下(例如在原始問題中),只需加入其中一個線程是不夠的。理想情況下,您需要等待所有工作線程完成,同時仍然將斷言失敗報告回主線程,例如Eyal的答案。

這裏有一個如何做到這一點使用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

我結束了使用這種模式它用的Runnable和線程工作。這主要是從@Eyal施奈德的回答啓發:

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

小的改進,以Eyal Schneider's answer
ExecutorService允許提交Callable任何拋出的異常或錯誤被退回Future被重新拋出。
因此,測試可以寫成:

@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 
}