JUnit源码分析(三)

三、微观——执行流程与代码风格

来过一遍JUnit的执行流程吧,这样你就能对JUnit有个清晰的认识,虽然作为一个使用者这完全是不必要的。从《JUnit in Action》直接拿来一张JUnit流程图。

哦,也许你看晕了,我来当下导游好了。上面已经提到了TestRunner是BaseTestRunner的子类,在三个不同的ui包中各有一个TestRunner。这里我们仅以junit.textui包中的为例。

TestRunner作为入口程序是怎么被启动的呢?习惯了使用容器的我们现在也许很少考虑这个问题。那我们在TestRunner类里面找找吧,你看,你发现了这个:

public static void main(String args[]) {

这不是我们写小桌面程序时经常打交道的main方法么?对,就这么简单。

从这个main方法入口,首先JUnit将要分析命令行的参数,然后将检查测试类是否包含符合标准的suite方法,如果有就将执行方法中的内容(见图中0、1部分);如果没有找到将自动生成一个TestSuite,这样就跳过了图中的0部分,并将测试用例类作为参数传入(见图中1部分)。

上面得到了一个TestSuite类型的对象,现在就可以运行测试了,不过在运行前要先加载TestResult和TestListener的对象(见图中2部分),用来监听和记录测试结果信息。剩下的在图中可以很容易的看懂了,你可以参照源码浏览一遍。 

这里提出我的一点疑问。注意到JUnit实践中提示将测试类中每个测试方法公用的初始化步骤放到setup方法中。这似乎会给你一种错觉,那就是你会认为setup与tearDown中的语句对于一个测试类中的所有测试方法只会运行一次。但是实际上JUnit在实现上却出乎意料,setup对于测试类中的每个测试方法都回运行一遍。意思就是说,你把公用的初始化代码放到setup方法中仅仅是在代码结构上实现了重用,而没有起到任何优化系统的作用。比如你在setUp中初始化数据库连接,那么这个过程将被执行不只一次,这可有点……。我们来看下代码:

//theClass为得到的TestCase类,name为此类其中的一个方法

static public Test createTest(Class theClass, String name) {

       Constructor constructor;

       try {

              constructor= getTestConstructor(theClass);

       ……

       Object test;

       try {

//以下内容为获得一个TestCase对象,并将方法名称传入这个对象

              if (constructor.getParameterTypes().length == 0) {

                     test= constructor.newInstance(new Object[0]);

                     if (test instanceof TestCase)

                            ((TestCase) test).setName(name);

              } else {

                     test= constructor.newInstance(new Object[]{name});

              }

       ……

       //返回这个对象

       return (Test) test;

}

再看下运行处的代码,下面的方法是运行在setUp和tearDown中间的

protected void runTest() throws Throwable {

       //fName就是testcase对象所拥有的那个方法的名称

       assertNotNull(fName);

       Method runMethod= null;

       try {

              //根据方法名由反射得到方法

              runMethod= getClass().getMethod(fName, null);

       }

       ……

              //执行测试方法

              runMethod.invoke(this, new Class[0]);

……

       }

这样每执行一个测试方法就要运行一遍setUp和tearDown方法,大概就是这样一个过程:

恩……,也许这是为了兼容老的版本,也许是……。还好,JUnit提供了一个补救的扩展类,那就是我们上面提到的TestSetup,在这里类里面真正的实现了setUp、tearDown方法的提取使用。你在使用的时候,通过继承来实现自己的setUp、tearDown方法,并使用装饰模式独有的调用方式来使用它就可以了。 

在阅读的过程中,代码风格上给我最明显的感觉就是,代码基本上全多做到了细化,将每个功能点单独提取到一个方法中,这样提高了代码的可复用性。可是在阅读的时候,在方法间频繁的跳跃,实在不是件好事,如果没有IDE的帮助,我非要晕掉不可。

因此我认为在提高代码重用上还是要坚持这样的一条原则:到必要的时候再下手。就是说,在你刚开始写代码的时候不要考虑什么重用和扩展,只有当你真正需要复用某段代码或者扩展系统时,在动手吧(记得在某位牛人的书上是这么来比喻的:让第一颗子弹打中你)。

JUnit中使用的是老版本java collection,这大概是因为JUnit最初版本出现的时候还没有新版collection推出。这种代码不应该出现在我们现在编写的代码中了,请注意。

 四、总结

 四、总结

 四、总结

    好了,基本上分析完了JUnit的代码,不知道你学到了什么。希望本文能够起到抛砖引玉的作用。

时间: 2024-10-03 12:52:58

JUnit源码分析(三)的相关文章

JUnit源码分析(四)——从Decorator模式说起

其实我这系列小文,名为源码分析,其实是自己读<设计模式>的读书笔记.Decorator模式在java的IO库中得到应用,java的IO库看起来复杂,其实理解了Decorator模式再回头看可以很好理解并使用.     Decorator模式,也就是装饰器模式,是对象结构型模式之一. 1.意图:动态地给一个对象添加一些额外的职责.给对象添加功能,我们首先想到的是继承,但是如果每增一个功能都需要继承,类的继承体系将无可避免地变的庞大和难以理解.面向对象设计的原则:优先使用组合,而非继承,继承的层次

JUnit源码分析 (三)——Template Method模式

在JUnit执行测试时,我们经常需要初始化一些环境供测试代码使用,比如数据库连接.mock对象等等,这些初始化代码应当在每一个测试之前执行并在测试方法运行后清理.在JUnit里面就是相应的setUp和tearDown方法.如果没有这两个方法,那么我们要在每个测试方法的代码内写上一大堆重复的初始化和清理代码,这是多么愚蠢的做法.那么JUnit是怎么让setUp和tearDown在测试执行前后被调用的呢?     如果你查看下TestCase方法,你会发现TestCase和TestSuite的run

JUnit源码分析(二)

    在上面我们已经提到了junit.extentions包中的内容TestSetup.来看看整个包的结构吧. 先简要的介绍下包中各个类的功能.ActiveTestSuite对TestSuite进行了改进,使得每个test运行在一个单独的线程里面,并且只到所有的线程都结束了才会结束整个测试.ExceptionTestCase是对TestCase进行的改进,可以方便的判断测试类是否抛出了期望的异常.而剩下的三个类,大概你看的出来是使用了装饰模式来设计的.其中TestDecorator为具体装饰类

QEMU1.3.0的源码分析三:user model之linux

从源码目录来看,user model有两块内容bsd-user和linux-user.我主要研究了下linux-user这种情况. 首先要提一下通常容易关注的焦点,linux-user下的函数入口点:/源码目录/linux-user/main.c中的 Line:3388    int main(int argc, char **argv, char **envp). 找到了入口函数,就可以根据这个main函数中的调用关系来看看这个情况下的主要执行流程和动作了. int main(int argc

JUnit源码分析(二)——观察者模式

    我们知道JUnit支持不同的使用方式:swt.swing的UI方式,甚至控制台方式,那么对于这些不同的UI我们如何提供统一的接口供它们获取测试过程的信息(比如出现的异常信息,测试成功,测试失败的代码行数等等)?我们试想一下这个场景,当一个error或者exception产生的时候,测试能够马上通知这些UI客户端:发生错误了,发生了什么错误,错误是什么等等.显而易见,这是一个订阅-发布机制应用的场景,应当使用观察者模式.那么什么是观察者模式呢? 观察者模式(Observer) Observ

第二人生的源码分析(三十一)接收数据的流量控制

数据接收回来后,本来就应立即处理掉,这样是比较简单的想法.但由于网络带宽有限,这时就需要限制UDP接收数据的速度.下面就来分析这种需求的实现,它的代码如下: #001 S32 LLPacketRing::receivePacket (S32 socket, char *datap) #002 { #003      S32 packet_size = 0; #004    下面判断是否使用接收的流量限制. #005      // If using the throttle, simulate

关于Junit源码的一些探索

Junit介绍 Junit 是由 Erich Gamma 和 Kent Beck 编写的一个开源的单元测试框架,主要用于白盒测试.Junit 的设计精简,易学易用,但是功能却非常强大. 在Junit3中,使用时只需继承TestCase类,并且 1. 测试方法名以test开头,返回void,方法中没有参数 2. 使用方法setUp()做初始化工作,使用方法tearDown()做清理工作 3. 使用Assert类的方法来判断测试用例结果 Junit源码分析 Junit的流程图: Junit的完整生命

Backbone源码分析(三)

Backbone源码分析(一)Backbone源码分析(二) Backbone中主要的业务逻辑位于Model和Collection,上一篇介绍了Backbone中的Model,这篇文章中将主要探讨Collection的源码. 让我们先来看一下Collection的构造函数: // Create a new **Collection**, perhaps to contain a specific type of `model`. // If a `comparator` is specified

定时组件quartz系列&lt;三&gt;quartz调度机制调研及源码分析

quartz2.2.1集群调度机制调研及源码分析引言quartz集群架构调度器实例化调度过程触发器的获取触发trigger:Job执行过程:总结:附:   引言 quratz是目前最为成熟,使用最广泛的java任务调度框架,功能强大配置灵活.在企业应用中占重要地位.quratz在集群环境中的使用方式是每个企业级系统都要考虑的问题.早在2006年,在ITeye上就有一篇关于quratz集群方案的讨论:http://www.iteye.com/topic/40970 ITeye创始人@Robbin在