多线程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 结合起来使用,效果还是相当不错的,并发测试变得简单多了,而且检测到冲突的可能性非常的大。

你可能感兴趣的:(Java,Performance,单元测试,junit,多线程,测试,classloader,eclipse)