Junit如何进行多线程测试

网友投稿 1279 2022-05-30

Junit和许多开源软件项目集成在一起,但是Junit执行多线程的单元测试有一些问题。这篇文章介绍Junit的一个扩展类库―――GroboUtils,这个类库被设计为来解决这些问题,并且使在Junit中进行单元测试成为可能。对Junit和线程有一个基本的理解是有好处的,但对于本篇文章的读者来说不是必需的。

介绍

如果你已经在一个开源的Java项目上工作,或者读了许多有关“极限编程”和其它“快速开发模式”的书籍,那么,你很有可能已经听说过有关Junit的事情。它是由Erich Gamma和Kent Beck编写的,Junit是一个Java的自动测试的框架,它允许你为你的软件定义的“单元测试”―――不管是测试程序还是功能代码,通常都是基于方法调用方法的。

Junit能在很多方面帮助你的开发团队―――在一些文章中已经包含了很多这方面的介绍。但从一个开者到另一个开发者,Junit实际上只专箸于两件事:

1、它强制你使用自己的代码。你的测试代码只是作为你的产品代码的客户端,从客户端的描述所获得的对你的软件的了解,能够帮助你标识出在API中的错误以及怎样改进代码,使其最终达到可以使用的目的。

2、它会给你对软件中改变带来信心,如果你的测试用例被中断,你就是立刻知道错误。在一天工作结束的时候,如果测试提示是绿色的,则代码是正确,你可以自信的检查它。

但是Junit不是解决所有软件测试中问题,第三方的扩展类库,例如HttpUnit,JwebUnit,XMLUnit等,已经认识到这些框架中不足,并且通过添加功能弥补不足,这些不足之一就是Junit不包含多线程的单元测试。

在这篇文章中,我们会看到一个很少有人知道的解决这个问题的扩展类库。我们通过建立Junit框架开始,并且运行一个例子来展示Junit在线程没试中的不足。在我们认识了Junit在线程测试方面的不足之后,我们通过一个使用GroboUtils框架的例子来讨论GroboUnitls

线程回顾

对于那些不熟悉线程的人来说,在这一点上是非常不安的(一点都不夸大),离开你的系统,我们将对线做一个简单的介绍。线程允许你的软件有多个任务,也就是说可以同时可做两件事情。

在Khalid Mugal和Rolf Rasmussen的书(A Programmer's Guide to Java Certification)中,对线程做了下面这样的简短描述:

一个线程是一个程序中的可执行单元,它是被独立执行的。在运行时,在程序中的线程

有一个公共的内存空间,因此,能够共享数据和代码;也就是说,它们是轻量级的。它

也共享正在运行程序的进程。

Java 线程使运行时环境异步,它允许不同的任务同时被执行。(p.272)

在web应用程序中,许多用户可能同时发请求给你的软件。当你写单元测试对你的代码进行压力测试时,你需要模拟许多并发事件,如果你在开发健壮的中间件,这样做是尤其重要的。对于这些组件,使用线程测试是一个好的想法。

不幸的是,Junit在这方面是不足的。

有关Junit和多线程测试的问题

如果你想验证下列代码,你需要下载并安装Junit。按着指示去做,以便能够在Junit的网站能够找到它。不要过分追求细节,我们将简要的介绍Junit是怎样工作的。要写一个Junit的测试,你必须首先创建一个扩展于junit.framework.TestCase(Juint中的基本测试类)的测试类。

Main()方法和suite()方法被用启动测试。无论是从命令行还是IDE集成开发环境窗口,必须确保junit.jar在你的CLASSPATH环境变量里指定。然后为BadExampleTest.Class类编译运行下列代码:

import junit.framework.*;

public class BadExampleTest extends TestCase {

// For now, just verify that the test runs

public void testExampleThread()

throws Throwable {

System.out.println("Hello, World");

}

public static void main (String[] args) {

String[] name =

{ BadExampleTest.class.getName() };

junit.textui.TestRunner.main(name);

}

public static Test suite() {

return new TestSuite(

BadExampleTest.class);

}

}

运行BadExampleTest来验证所建立的每一件事情的正确性。一旦,main()被调用,Junit框架将自动的执行任意一个用“test”开关命名的方法。继续并试着运行测试类。如果你正确的做了每一件事,它应该在输出窗口打印出“Hello World”。

现在,我们要给程序添加一个线程类。我将通过扩展java.lang.Runnable接口来做这件事情。最后,我们将改变策略,并且扩展一个使线程自动创建的类。

在DelayedHello的构造器中,我们创建一个新的线程并且调用它的start()方法。

import junit.framework.*;

public class BadExampleTest extends TestCase {

private Runnable runnable;

public class DelayedHello

implements Runnable {

private int count;

private Thread worker;

private DelayedHello(int count) {

this.count = count;

worker = new Thread(this);

worker.start();

}

public void run() {

try {

Thread.sleep(count);

System.out.println(

"Delayed Hello World");

} catch(InterruptedException e) {

e.printStackTrace();

}

}

}

public void testExampleThread()

throws Throwable {

System.out.println("Hello, World");       //1

runnable = new DelayedHello(5000);        //2

System.out.println("Goodbye, World");     //3

}

public static void main (String[] args) {

String[] name =

{ BadExampleTest.class.getName() };

junit.textui.TestRunner.main(name);

}

public static Test suite() {

return new TestSuite(

BadExampleTest.class);

}

}

testExampleThread()方法实际上称不上是一个测试方法,实际上,你想使测试自动化,并且不想把检查结果输出到控制台,但是,这里却是这样的,因此,这一点示范了Junit是不支持多线程的。

注意:testExampleThread()方法执行三项任务:

1、    打印“Hello,World”;

2、    初始化并起动一个支持打印“Delayed Hello World.”线程;

3、    打印“Goodbye,World”。

如果你运行这个测试类,你会注意到一些错误。TextHellWorld()方法像你期望的那样运行和结束。它没有发出任何有关线程的异常,但是你却不会接受到来自线程的返回信息。注意,你不会看到“Delayed Hello World”。为什么?因为线程还在激活状态的时候,Junit已经执行完成。问题发生在下面这行,使线程执行结束的时候,你的测试不能反映出它的执行结果。这个问题行是在Junit的TestRunner中。它没有被设计成搜寻Runnable实例,并且等待这些线程发出报告,它只是执行它们并且忽略了它们的存在。因为这个原因,几乎不可能在Junit中编写和维护多线程的单元测试。

进入GroboUtils

GroboUtils是Matt Albrecht编写的一个开源项目,它的目标是扩展Java的测试可能性。GroboUtils被发布在MIT许可下,这使它可以很友好的包含到其它的开源项目中。

Grobo TestingJUnit 子项目

GroboUtils被列入与同类测试方面有关的试验的子项目。这篇文章的焦点集中在Grobo TestingJUnit 子项目,它为Junit引入了一个支持多线程测试的扩展类库。(这个子项目还引入了集成测试和严重错误的概念,但是这些特征超出了这篇文章所讨论的范围。)

在GroboTestingJUnit子项目内是BroboTestingJUnit-1.1.0-core.jar类库,它包含了MultiThreadedTestRunner和TestRunnable类,这两个类是对Junit进行扩展处理多线程测试所必须的。

TestRunnable类

TestRunnalbe类扩展了junit.framework.Assert类并且实现了java.lang.Runnable接口。你可以在你的测试类内定义TestRunnable对象做为内隐类。虽然,传统的线程类实现一个run()方法,但是你的嵌套TestRunnable类必须实现runTest()方法来替代run()方法。这个方法将被MultiThreadedTestRunner类在运行时调用,因此你不应该在构造器中调用它。

MultiThreadedTestRunner类

MultiThreadedTestRunner是一个允许把异步运行的线程数组放入Junit内一个框架。这个类在它的构造器中接受一个TestRunnable实例的数组做为参数。一旦建立了这个类的一个实例,它的runTestRunnables()方法就应该被调用开始执行线程测试。

和标准的JunitTestRunner不一样,MultiThreadedTestRunner将等待,直到所有的线程执行终止退出。这样就强制Junit在线程执行任务的时候进行等待,从而巧妙的解决了我们前面提出的问题。让我们来看一下GroboUtils和Junit是怎样集成的。

编写多线程测试

现在把上面例子中的内隐类扩展自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);

}

}

这时,我们全然不用创建工作线程。MultiThreadedTestRunner将在底层做这件事情,你重写runTest()方法来替实现run()方法,runTest()方法被后面的MultiThreadedTestRunner类调用―――我们自己不会调用它。

一旦TestRunnable被定义,我们必须定义新的测试用例。在我们的testExampleThread()方法中,我们实例化了几个TestRunnable对象,并且把它们添加到一个数组中。然后,示例化MultiThreadedTestRunner类,把TestRunnable对象数组做为参数传递给这人类的构造子函数。现在,我们有了一个MultiThreadedTestRunner类的实例,我们就可以调用它的runTestRunnables()方法来执行测试。

MultiThreadedTestRunner(和Junit中的TestRunner不一样)在继续执行之前,将等待每一个线程运行终止。它也为通过构造器传递给它的每个TestRunnalbe对象创建工作线程并且调用异步的start()方法。这就意味着你没有必要通过创建你自己的线程来跳过这个障碍―――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);

}

}

/**在你的测试用例中使用MultiThreadedTestRunner,

* MTTR需要一个TestRunnable对象做为它的构造器的参数

* MTTR创建以后,调用runTestRunnables()方法来运行它

*/

public void testExampleThread()

throws Throwable {

//实例化 TestRunnable 类

TestRunnable tr1, tr2, tr3;

tr1 = new DelayedHello("1");

tr2 = new DelayedHello("2");

tr3 = new DelayedHello("3");

//把实例传递给 MTTR

TestRunnable[] trs = {tr1, tr2, tr3};

MultiThreadedTestRunner mttr =

new MultiThreadedTestRunner(trs);

//执行MTTR和线程

mttr.runTestRunnables();

}

/**

* 标准的 main() 和 suite() 方法

*/

public static void main (String[] args) {

String[] name =

{ ExampleTest.class.getName() };

junit.textui.TestRunner.main(name);

}

public static Test suite() {

return new TestSuite(ExampleTest.class);

Junit如何进行多线程测试

}

}

上面的例子中,每个线程将会在你发出测试指令后,在2到5秒之间向你返回它们的输出,它们不仅按时间显示,而且是以一个随机的顺序来显示。这个单元测试只有所有的线程都执行完成后才会结束。由于外加了MultiThreadedTestRunner,所以Junit继续执行测试用例之前,必须耐心的等待TestRunnables执行完成它们的工作,做为可选项,你可以为MultiThreadedTestRunner的执行分配最大的执行时间(这样以便你脱离线程,而不挂起测试)。

要编译运行ExampleTest,你必须在你的CLASSPATH环境变量中指定junit.jar和GroboUtils-2-core.jar两个类库的位置。这样你就会看到每人线程以随机的顺序来输出 “Delayed Hedllo World”

结束语

写一个多线程的单元测试不用感到苦脑,GroboUtils类库为编写多线程的单元测试提供了一个清晰简单的API接口,通过把这个类库添加到你的工具包中,你就可以把单元测试扩展到模拟繁重的WEB网络通讯和并发的数据库处理,以及对你的同步方法进行压力测试。

更多面试题请狠狠的点击 下载

junit 任务调度 多线程

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:程序员的修仙之路——“设计模式之道”!
下一篇:linux常见面试题
相关文章