多線程JUnit單元測試:GroboUtils and ConTest

http://www.cnblogs.com/shwen99/archive/2010/03/22/1691280.html


“並行程序易於產生 bug 不是什麼祕密。編寫這種程序是一種挑戰,並且在編程過程中悄悄產生的 bug 不容易被發現。許多並行 bug 只有在系統測試、功能測試時才能被發現或由用戶發現。到那時修復它們需要高昂的費用 -- 假設能夠修復它們 -- 因爲它們是如此難於調試。”以上論述來自IBM論壇中關於 ConTest 的一篇介紹文章,並且,我還要補充一點,這種 BUG 通常根本無法重現,以至於要找到發生 BUG 的原因都是非常的困難;即使幸運的找到了可能原因,修改了代碼,要確認問題確實得到了解決,依然是非常的困難,因爲根本無法進行有效的測試。

 

JUnit提供了很好的單元測試框架,但是對於多線程的併發測試,卻無能爲力。經過一番搜索,找到了一個叫 GroboUtils 的東西,關於它的介紹不少,這裏不再重複,GroboUtils 解決了啓動多線程以及當某個線程出錯時將錯誤返回給 JUnit 的問題,現在我們可以比較簡單啓動多個線程並驗證結果了。具體介紹請自己 google 一下,我這裏簡單的貼一個 Demo 代碼,當然和官方以及一般的例子不同,我比較喜歡使用內部匿名類來啓動線程。這個測試很簡單,啓動 5 個線程,併發的進行 i++ 運算,我有意的註釋掉了同步代碼,這樣應該會導致最後測試無法通過。

 

class ParalInc {
    
public int i=0;    
}

public class ParalIncTest {

    
public static class Incrementor extends Thread {
       
public void run() {
       }
    }

    @Test
    
public void testInc() throws Throwable {
        
for (int i = 0; i < 1000; i++) {
            
final ParalInc target = new ParalInc();
            
            TestRunnable r 
= new TestRunnable() {
                
public void runTest() throws Throwable {
//                synchronized(ParalIncTest.class) {
                    target.i++;
//                }
                }
            };
            
            TestRunnable[] tcs 
= {r, r, r, r, r};
            
int threadCount = tcs.length;
            
            MultiThreadedTestRunner mttr 
= new MultiThreadedTestRunner( tcs );
            mttr.runTestRunnables( 
2 * 60 * 1000 );
            
            System.out.println(
"final value: " + target.i);
            
            
if (target.i != threadCount) {
                System.err.println(
"   Bug - at loop " + i);
            }
            
            assertEquals(threadCount, target.i);
        }
    }
}


開始的時候我並沒有添加最外層循環,然而測試的結果很遺憾,怎麼都無法讓這段代碼出錯。加上循環以後,才偶爾會出錯,也一般要循環到數百次以後纔會發生錯誤。導致這種結果的原因,估計是線程的切換其實併發象我們想象的那麼頻繁,而且要恰恰在共享衝突點發生切換,這個概率也確實非常的小。如果能夠控制虛擬機的線程切換,使得共享衝突概率更大一些那該是多麼好啊。

 

因此,我找到了這篇文章 http://www.ibm.com/developerworks/cn/java/j-contest.html ,ConTest,這真是個好東西啊,配置好以後,還是上面那段測試代碼,但是測試結果幾乎都會檢測到衝突,而且通常是在循環了2-3次後就檢測到了。

 

ConTest 下載鏈接在從上面的文章中可直接找到,可以用 Eclipse plug-in 方式安裝,update 地址 http://awwebx04.alphaworks.ibm.com/ettktechnologies/updates 。但是我實在沒發現它給 Eclipse 添加了什麼功能,其實我覺得主要還是隻要下載到那個 ZIP 包就可以了,並不需要安裝爲 plug-in。

 

下載下來以後,ConTest 並不能自動的開始工作,需要一點點配置工作,這有點煩人。

 

首先要將 ConTest.jar 包拷貝到一個便於指定位置的地方,我一般傾向於將它放在工程目錄的lib子目錄下,另外 ZIP 包的 LIB 目錄下還有一個 KingProperties 文件,要拷貝到和 ConTest.jar 相同的目錄中。修改 KingProperties 文件,當然也可以不改,我主要是修改 output 目錄,爲 output = target,和 maven 保持一致,一切都輸出到那裏去。

 

這還只是一些準備活動,最後,要讓 ConTest 開始工作,還需要修改 Run|RunConfigurations,在單元測試的 jvm 啓動參數中指定啓動 ConTest , 既添加 JVM 參數:

-javaagent:lib/ConTest.jar -Dcontest.targetClasses=demo/ParalIncTest

這裏 -Dcontest.targetClasses=demo/ParalIncTest 既指定要進行並行性分析的類名,可以用逗號分隔指定多個類,當然這個參數本身也可以 KingProperties 文件中指定的,但是我覺得在命令行測試比較方便一些,畢竟從單元測試的角度來說,一次應該只測試一個類。在命令行測試,就不需要多套 KingProperties 配置文件。

 

至此,所有配置工作完成,依然像以前一樣啓動單元測試,ConTest 作爲 javaagent 裝載,因此它其實是在 ClassLoader 加載類字節碼的時候介入,修改字節碼在其中加入一些線程切換的代碼,這樣線程切換更頻繁了,特別在一些同步塊中,發生同步衝突的概率自然就大很多了。

 

總的來說,將 GroboUtils 和 ConTest 結合起來使用,效果還是相當不錯的,併發測試變得簡單多了,而且檢測到衝突的可能性非常的大。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章