单元测试JUnit

 

作为一个底层编码人员来说,编写一个足够简单的方法,通常并不会想到测试。我这里所说的“足够简单”并不是指业务逻辑,而是指程序自身的结构,它没有复杂的条件判断,不涉及太多的变量,也没有复杂的递归与繁琐的数据结构,程序调用这样的方法一般不会有太多的变数。但是一般象这样的方法在程序中一般是比较少的,所以很多方法都是需要测试一下,但是为了方便或者赶时间(有时候也是为了偷懒),只是在程序逻辑不正确或者程序报异常才会想到去测试下可能出现问题的方法。而测试最常见的方法一般就是用一个单独main方法来运行想要测试的方法,然后看输出情况;对于一个J2EE程序来说,很多方法是不可以用或者很难用main方法执行以进行测试,大多数时候是根据一次有效的请求与响应使待测试的方法得以运行,然后根据控制台输出信息来找出程序的缺陷。如果逻辑比较复杂的时候可以借助调试工具来发现问题。

上面谈到的少量测试,当然也是一种测试,但是它很难规模化(一次就运行一个用例),也很难自动化(要通过人去判断),用例都是临时性的无法重复利用。JUnit是什么东西呢?我个人认为就是把这样一些零散的测试有机组织在一起的一个框架,使前面所说的那些小打小闹上升到一个更高的层次,测试用例可以重复使用,测试可以自动化执行,当然还有其它一些好处。

左图是JUnit框架的核心构架:上面的TestTestCaseTestSuit是一个组合模式;而下面的TestResultBaseTestRunnerTestListener是一个观察者模式。TestCase继承Assert类,实现Test接口,同时它依赖TestResult类,这个依赖实际是Test接口所规定的,当运行一个TestCase时需要一个TestResult实例;TestSuit类实现了Test接口,它表现为TestCase的聚集,而实际是Test的聚集,TestSuiteBaseTestRunner运行的实体, 但最终TestSuite还是委托TestCase来完成实际的运行;TestResult也是一个很重要的类,它主要用于收集测试的结果,并向所有注册的TestListerner监听器广播测试的及时情况,所有TestRunner都是一个监听器,所以它不仅可以从TestResult获取测试的最终情况,公平可以知道测试的即时情况。TestCaseTestSuitTestResult这三个类在测试运行时它们是最活跃,最核心的类。整个测试运行一个大致过程是这样的:首先由一个TestRunner启动运行,TestRunner必须获取一个运行的对象,这个对象就是TestSuite(关于TestSuite怎样建立与获取后文详细介绍);获取TestSuite以后TestRunner就会委托它运行它的run方法,这个方法是一个公有方法即:public void run(TestResult result) 这个方法的参数是一个TestResult对象,也是由TestRunner产生的,用来收集测试结果的;它是一个很重要的参数,在产生这个TestResult对象之后TestRunner会做一件很重要的事情,那就是向这个产生的TestResult对象注册自己,以便监听测试运行的情况;TestRunner委托TestSuite对象运行时,TestSuite对象就会对自己保留的多个Test对象(可能是TestCase也可能是TestSuite)进行循环递归遍历,当对象是TestSuite是就递归(实际也是执行它的run方法),当对象是TestCase时就委托这个TestCase对象来执行它的run方法这个方法实际也是public void run(TestResult result)这个方法是Test接口所规定的一个方法,如此层层递归就会将运行委托给包含在TestSuite对象中的每一个TestCase对象,上面说TestTestCaseTestSuit构成一个组合模式的精髓也在于此,它们的关系就象文件与文件夹的关系;当运行委托给一个具体的TestCase对象后,此TestCase对象会从run方法参数获取最初传入的TestResult对象,此时的TestCase对象并不执行自己名称所标识的测试方法,而是将运行进一步委托给传入的TestResult对象,并且将自己作为参数传递给TestResult对象的run方法,此方法的签名为:protected void run(final TestCase test)之所以这么做主要是为了让TestResult对象容易收集测试结果;TestResult对象获取运行控件权之后,TestResult对象首先就会通知各监听器,一个测试(指对一个方法的测试)开始了,因为TestResult对象在获取运行控件权时获取了一个TestCase对象,这个对象就是此次测试所要测试的对象(说的更具体一点,是要运行这个TestCase对象中的一个方法,具体是哪个方法要依赖构造TestCase对象时设置的唯一名称标识,实际是方法名),因此,在通知各监听器之后,TestResult对象就是会执行TestCase对象的runBare方法,来执行实际的测试runBare方法没有任何参数,runBare方法首先执行资源管理方法setUp(),这是任何一个测试都会执行的一个环境预设方法,紧接着就会执行待测试的方法(这个方法是通过反射取得,它的外层用一个方法包装,方法签名为:protected void runTest() throws Throwable),执行所有测试方法实际都是执行这个方法,执行完成之后,再执行资源管理方法tearDown()方法以释放资源,这就是runBare方法执行的全过程;runBare方法执行完后,TestResult对象就会收集测试结果,即执行addFailureaddError方法,之后就会通知各监听器,此次测试已经完成,即执行endTest方法,至此宣告一次测试完成,如果最初获取的TestSuite还“含有”其它TestCase对象,那测试就会从第步开始进入下一轮,否则就会终止测试。

下图是对这一执行过程的时序描述

 

 

时序图 上面对JUnit的核心构架及运行情况进行了粗略的分析,下面就在实际使用中可能出现的问题,以及一些常见的疑问汇集如下:

A.       TestRunner如何获取TestSuite

TestRunner在启动时候一般都会要求传递一个参数,这个参数最常见的一种就是一个继承于TestCase类的类名,至于TestRunner究竟可以跟什么样的参数没有研究过,但根据我所知的情况至少有两种情况,一个就是继承于TestCase类的类名,另一个就是跟TestSuite类名。这里要特别说明的一点TestSuite类并不是一个继承于TestSuite的类而是具有一个特定方法签名的类,这个特定的方法签名为:public static Test suite();这样的方法被称之为Test Suite至少Eclipse上是这样的,Eclipse说新建一个Test Suite就是新建一个具有此方法签名的类,这确实很难让人理解,把方法的签名都写死了。

这个方法为什么如此重要呢?因为TestRunner要靠这个方法来提供一个Test(大多数情况是一个TestSuite而不是一个TestCase)它被看这一组测试的根。一个典型的Test Suite如下:

package qw.zxc.junit;

 

import junit.framework.Test;

import junit.framework.TestSuite;

 

/**

 * @author Administrator

 * 主要是为了验证一个TestSuite的建立,体会何为"建立一个TestSuite"

 */

public class AllTests {

 

   public static Test suite() {

      TestSuite suite = new TestSuite("测试一个TestCase自带suite方法的情况");

      //$JUnit-BEGIN$

      suite.addTest(TestCalculate.suite());

      suite.addTestSuite(TestCalculate2.class);

      //$JUnit-END$

      return suite;

   }

 

}

一个TestCase中就也可以有这样一个方法,这个方法就是告诉TestRunner要运行哪些测试方法,但是TestCase中并不一定要有这个方法,那么如果把一个TestCase传给TestRunner,并且这个类不含有这个方法,TestRunner是如何得知要运行哪些测试方法呢?原因是这样的,TestRunner接收 一个TestCase类名参数时,并且这个类没有方法签名public static Test suite();TestRunner会利用反射将所有以test开头的方法集中起来放入一个TestSuite类对象中,以此对象作为返回对象以待运行其中的测试方法如果TestRunner接收 一个TestCase类名参数时,但含有方法public static Test suite();TestRunner就不会利用反射,而是直接调用此方法,来获取一个Test对象以运行其中的测试方法如果TestRunner接收 一个含有方法签名:public static Test suite();的类名时(它不一TestCase也不一定是TestSuite),它也会直接调用此方法来获取一个Test对象以运行其中的测试方法。

B.       如何自己构建一个有效TestSuite

通过上面的分析可知,不管是上面的哪一情况,最终TestRunner都会想尽办法从给定的参数获取一个TestSuite类对象(标准说法应该是Test对象,但是绝大多数情况下是TestSuite对象,而不会是一个TestCase对象),那么如何构建一个有效TestSuite类对象呢?主要可以从以下几个方法签名入手去考虑:

public TestSuite();

public TestSuite(String name);

public TestSuite(final Class theClass);

public TestSuite(Class theClass, String name);

public void addTest(Test test);

public void addTestSuite(Class testClass);

public TestCase();

public TestCase(String name);

这里总共有八个方法前六个属于TestSuite,后两个属于TestCase,下面对上面八个方法作一个详细的说明:

*         第一个方法:public TestSuite();是构造一个空的TestSuite其中不包括任何待运行方法;

*         第二个方法:public TestSuite(String name);实际与第一个方法类似,它只是为这一组集中在一起测试命了一个名字,一般这个名字应该与这组测试功能相关的有意义的名字;

*         第三个方法:public TestSuite(final Class theClass);这个方法比较重要,这里的参数是一个Class类型,但它必须是一个TestCase型,此方法作用就是通过反射来构造一个TestSuite类对象,其中已经包含了给定的TestCase类的所有以test开头的方法,注意这里不管这个TestCase是否具有方法签名:public static Test suite();一律使用反射而不会使用“有则调用;无则反射”的规则。

*         第四个方法:public TestSuite(Class theClass, String name); 实际与第三个方法一样,只是多了一个给这组测试起一个自己喜欢的名字。

*         第五个方法:public void addTest(Test test);这是一个比较灵活的方法,它表示向一个TestSuite中添加要运行的方法,这里的参数是Test所以它既可以是一个TestSuite,与可以是一个TestCase,但是这里如果是TestCase千万要小心,可以先看一下第七与第八个方法,这两个方法是构造一个TestCase,我们说构造一个TestCase并不难,但是构构造一个添加到TestSuite中能够正常执行的TestCase并不是随便new一下就可以了,也不是一个随便的名字就可以的,我们说在TestSuite中的测试方法运行依赖的是反射,要知道运行的是TestCase中的哪一个测试方法是用的TestCasename标识的,所以在构造TestCase时必须给出正确的name标识,这个标识必须是你想运行的那个测试方法的名字。这好象是添加单个方法的唯一方法。

*         第六个方法:与第三个方法相关,它实际是利用第三个先构造一个TestSuite然后再调用第五个方法将构造TestSuite添加到当前的TestSuite

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值