testExampleThread()方法實(shí)際上稱不上是一個測試方法,實(shí)際上,你想使測試自動化,并且不想把檢查結(jié)果輸出到控制臺,但是,這里卻是這樣的,因此,這一點(diǎn)示范了Junit是不支持多線程的。
注意:testExampleThread()方法執(zhí)行三項(xiàng)任務(wù):
1、 打印“Hello,World”;
2、 初始化并起動一個支持打印“Delayed Hello World.”線程;
3、 打印“Goodbye,World”。
如果你運(yùn)行這個測試類,你會注意到一些錯誤。TextHellWorld()方法像你期望的那樣運(yùn)行和結(jié)束。它沒有發(fā)出任何有關(guān)線程的異常,但是你卻不會接受到來自線程的返回信息。注意,你不會看到“Delayed Hello World”。為什么?因?yàn)榫程還在激活狀態(tài)的時候,Junit已經(jīng)執(zhí)行完成。問題發(fā)生在下面這行,使線程執(zhí)行結(jié)束的時候,你的測試不能反映出它的執(zhí)行結(jié)果。這個問題行是在Junit的TestRunner中。它沒有被設(shè)計(jì)成搜尋Runnable實(shí)例,并且等待這些線程發(fā)出報告,它只是執(zhí)行它們并且忽略了它們的存在。因?yàn)檫@個原因,幾乎不可能在Junit中編寫和維護(hù)多線程的單元測試。
進(jìn)入GroboUtils
GroboUtils是Matt Albrecht編寫的一個開源項(xiàng)目,它的目標(biāo)是擴(kuò)展Java的測試可能性。GroboUtils被發(fā)布在MIT許可下,這使它可以很友好的包含到其它的開源項(xiàng)目中。
Grobo TestingJUnit 子項(xiàng)目
GroboUtils被列入與同類測試方面有關(guān)的試驗(yàn)的子項(xiàng)目。這篇文章的焦點(diǎn)集中在Grobo TestingJUnit 子項(xiàng)目,它為Junit引入了一個支持多線程測試的擴(kuò)展類庫。(這個子項(xiàng)目還引入了集成測試和嚴(yán)重錯誤的概念,但是這些特征超出了這篇文章所討論的范圍。)
在GroboTestingJUnit子項(xiàng)目內(nèi)是BroboTestingJUnit-1.1.0-core.jar類庫,它包含了MultiThreadedTestRunner和TestRunnable類,這兩個類是對Junit進(jìn)行擴(kuò)展處理多線程測試所必須的。
TestRunnable類
TestRunnalbe類擴(kuò)展了junit.framework.Assert類并且實(shí)現(xiàn)了java.lang.Runnable接口。你可以在你的測試類內(nèi)定義TestRunnable對象做為內(nèi)隱類。雖然,傳統(tǒng)的線程類實(shí)現(xiàn)一個run()方法,但是你的嵌套TestRunnable類必須實(shí)現(xiàn)runTest()方法來替代run()方法。這個方法將被MultiThreadedTestRunner類在運(yùn)行時調(diào)用,因此你不應(yīng)該在構(gòu)造器中調(diào)用它。
MultiThreadedTestRunner類
MultiThreadedTestRunner是一個允許把異步運(yùn)行的線程數(shù)組放入Junit內(nèi)一個框架。這個類在它的構(gòu)造器中接受一個TestRunnable實(shí)例的數(shù)組做為參數(shù)。一旦建立了這個類的一個實(shí)例,它的runTestRunnables()方法應(yīng)該被調(diào)用開始執(zhí)行線程測試。
和標(biāo)準(zhǔn)的JunitTestRunner不一樣,MultiThreadedTestRunner將等待,直到所有的線程執(zhí)行終止退出。這樣強(qiáng)制Junit在線程執(zhí)行任務(wù)的時候進(jìn)行等待,從而巧妙的解決了我們前面提出的問題。讓我們來看一下GroboUtils和Junit是怎樣集成的。
編寫多線程測試
現(xiàn)在把上面例子中的內(nèi)隱類擴(kuò)展自net.sourceforge.groboutils.junit.vl.TestRunnable包,我們必須像下面這樣來重寫runTest()方法。
private class DelayedHello
extends TestRunnable {
private String name;
private DelayedHello(
String name) {
this.name = name;
}
public void runTest() throws Throwable {
long l;
l = Math.round(2 + Math.random() * 3);
// Sleep between 2-5 seconds
Thread.sleep(l * 1000);
System.out.println(
"Delayed Hello World " + name);
}
}
這時,我們?nèi)徊挥脛?chuàng)建工作線程。MultiThreadedTestRunner將在底層做這件事情,你重寫runTest()方法來替實(shí)現(xiàn)run()方法,runTest()方法被后面的MultiThreadedTestRunner類調(diào)用———我們自己不會調(diào)用它。
一旦TestRunnable被定義,我們必須定義新的測試用例。在我們的testExampleThread()方法中,我們實(shí)例化了幾個TestRunnable對象,并且把它們添加到一個數(shù)組中。然后,示例化MultiThreadedTestRunner類,把TestRunnable對象數(shù)組做為參數(shù)傳遞給這人類的構(gòu)造子函數(shù),F(xiàn)在,我們有了一個MultiThreadedTestRunner類的實(shí)例,我們可以調(diào)用它的runTestRunnables()方法來執(zhí)行測試。
MultiThreadedTestRunner(和Junit中的TestRunner不一樣)在繼續(xù)執(zhí)行之前,將等待每一個線程運(yùn)行終止。它也為通過構(gòu)造器傳遞給它的每個TestRunnalbe對象創(chuàng)建工作線程并且調(diào)用異步的start()方法。這意味著你沒有必要通過創(chuàng)建你自己的線程來跳過這個障礙———MultiThreadedTestRunner會為你做這件事。下面是ExampleTest的終版:
import junit.framework.*;
import net.sourceforge.groboutils.junit.v1.*;
public class ExampleTest extends TestCase {
private TestRunnable testRunnable;
private class DelayedHello
extends TestRunnable {
private String name;
private DelayedHello(
String name) {
this.name = name;
}
public void runTest() throws Throwable {
long l;
l = Math.round(2 + Math.random() * 3);
// Sleep between 2-5 seconds
Thread.sleep(l * 1000);
System.out.println(
"Delayed Hello World " + name);
}
}