測試并行程序與以往有什么不同 ?
隨著多核的普及,并行程序的開發(fā)已經(jīng)提上日程。相對串行程序而言,并行程序更有可能出錯。一方面,并行程序的執(zhí)行序列具有很強(qiáng)的隨機(jī)性,線程交錯執(zhí)行的序列可能每次都不一樣,而只要一個序列有問題,整個程序是不正確的。另一方面,并行程序?qū)Υ蠖鄶?shù)程序員來說,都是一個新的領(lǐng)域,經(jīng)驗相對較少,這是容易出錯的另外一個因素。
既然如此,我們更需要仔細(xì)的測試我們的并行程序和組件了。目前已經(jīng)有一些 JUnit 擴(kuò)展可以創(chuàng)建多個線程,同時運行多個測試用例,從而加快測試用例集的執(zhí)行速度,如 p-unit 。但對于測試并行程序和組件,這些功能并不能滿足所有的需求。因為開發(fā)人員通常希望可以精確地控制多個線程之間的同步。
與測試順序程序相同,我們希望能在測試并行操作之前,首先準(zhǔn)備一些測試數(shù)據(jù)。然后,啟動多個線程測試執(zhí)行不同的操作。后,等待所有線程結(jié)束之后,檢驗結(jié)果的正確性。在第二階段中,多個線程能以任意的次序交錯執(zhí)行。結(jié)果的正確性檢查應(yīng)與線程的執(zhí)行測序無關(guān)。
標(biāo)準(zhǔn) JUnit 只捕捉來自主線程的 Exception 。而其他線程中產(chǎn)生的 Exception 則會安靜地被忽略掉,使得我們在子線程運行出錯的情況下仍舊能得到“ Green Bar ”。這顯然不是程序員喜歡的測試行為,我們希望測試結(jié)果能正確地反映所有線程的運行結(jié)果。
這種用于并行程序的測試模式會在測試并行程序時會不斷的重復(fù)。如果開發(fā)人員每次都需要重復(fù)創(chuàng)建這些框架,不僅繁瑣,而且容易引入錯誤。通過使用以下介紹的簡單擴(kuò)展,可以使并行程序的測試變得和順序程序一樣簡單。這種擴(kuò)展并不影響 JUnit 的其他特性以及各種 IDE 的 JUnit 插件的使用。
下載并使用擴(kuò)展框架
首先,我們給出一個使用新擴(kuò)展進(jìn)行并行測試的例子。
例 1. 使用 JUnit 擴(kuò)展進(jìn)行并行測試
/**
* @author Zhi Gan
*
*/
@RunWith(Parallelized.class)
@ParallelSetting(threadNumber = { 1, 2, 4, 8 })
public class TestThreaded {
Set<String> strSet;
@Before
public void setUp() {
strSet = new LockFreeSet();
}
@Test
public void doNothing() {
}
@InitFor("testThread")
public void putSomeData(int size){
strSet.add("putSomeData");
}
@Threadedpublic void testThread(int rank, int size) {
// every thread adds element to set
strSet.add("abcde" + rank);
}
@CheckFor("testThread")
public void checkResult(int size) {
assertEquals(size+1, strSet.size());
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++)
JUnitCore.runClasses(TestThreaded.class);
}
}
如果我們在 Eclipse 中運行測試,那么測試完畢之后的 JUnit 視圖如下所示:
圖 1. 并行測試的完成結(jié)果
Sample figure containing an image
接下來,我們模擬子線程在運行時拋出異常。
例 2. 子線程運行時異常
/**
* @author Zhi Gan
*
*/
@RunWith(Parallelized.class)
@ParallelSetting(threadNumber = { 1, 2, 4, 8 })
public class TestThreaded {
Set<String> strSet;
@Before
public void setUp() {
strSet = new LockFreeSet();
}
@Test
public void doNothing() {
}
@InitFor("testThread")
public void putSomeData(int size){
strSet.add("putSomeData");
}
@Threadedpublic void testThread(int rank, int size) {
// throw a runtime error in spawned thread
throw new RuntimeError();
}
@CheckFor("testThread")
public void checkResult(int size) {
assertEquals(size+1, strSet.size());
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++)
JUnitCore.runClasses(TestThreaded.class);
}
}