技术开发 频道

分析 JUnit 框架源代码

  测试驱动运行阶段(运行所有 TestXXX 型的测试方法)

  由图 7 所示 , 我们可以知道初始化完毕,即 testsuit() 创建好后 , 便进入方法 :

 

  doRun(suite, wait);

  代码如下 :

  图 14. doRun 函数代码  

  该方法为测试的驱动运行部分,结构如下:

  ·创建 TestResult 实例。

  ·将 junit.textui.TestRunner 的监听器 fPrinter 加入到 result 的监听器列表中。

  其中,fPrinter 是 junit.textui.ResultPrinter 类的实例,该类提供了向控制台输出测试结果的一系列功能接口,输出的格式在类中定义。 ResultPrinter 类实现了 TestListener 接口,具体实现了 addError、addFailure、endTest 和 startTest 四个重要的方法,这种设计是 Observer 设计模式的体现,在 addListener 方法的代码中:  

public synchronized void addListener(TestListener listener) {

  fListeners.addElement(listener);

  }

  将 ResultPrinter 对象加入到 TestResult 对象的监听器列表中,因此实质上 TestResult 对象可以有多个监听器显示测试结果。第三部分分析中将会描述对监听器的消息更新。

  计时开始。

  run(result) 测试运行。

  计时结束。

  统一输出,包括测试结果和所用时间。

  其中最为重要的步骤为 run(result) 方法,代码如下。

  图 15. run 函数代码  

  Junit 通过 for (Enumeration e= tests(); e.hasMoreElements(); ){ …… } 对 TestSuite 中的整个“树结构”递归遍历运行其中的节点和叶子。此处 JUnit 代码颇具说服力地说明了 Composite 模式的效力,run 接口方法的抽象具有重大意义,它实现了客户代码与复杂对象容器结构的解耦,让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器。每次循环得到的节点 test,都同 result 一起传递给 runTest 方法,进行下一步更深入的运行。

  图 16. junit.framework.TestResult.run 函数代码  

  这里变量 P 指向一个实现了 Protectable 接口的匿名类的实例,Protectable 接口只有一个 protect 待实现方法。而 junit.framework.TestResult.runProtected(Test, Protectable) 方法的定义为:  

public void runProtected(final Test test, Protectable p) {

  try {

  p.protect();

  }

  catch (AssertionFailedError e) {

  addFailure(test, e);

  }

  catch (ThreadDeath e) {
// don't catch ThreadDeath by accident

  throw e;

  }

  catch (Throwable e) {

  addError(test, e);

  }

  }

  可见 runProtected 方法实际上是调用了刚刚实现的 protect 方法,也就是调用了 test.runBare() 方法。另外,这里的 startTest 和 endTest 方法也是 Observer 设计模式中的两个重要的消息更新方法。

  以下分析 junit.framework.TestCase.runBare() 方法:

  图 17. junit.framework.TestCase.runBare() 函数代码  

  在该方法中,最终的测试会传递给一个 runTest 方法执行,注意此处的 runTest 方法是无参的,注意与之前形似的方法区别。该方法中也出现了经典的 setUp 方法和 tearDown 方法,追溯代码可知它们的定义为空。用户可以覆盖两者,进行一些 fixture 的自定义和搭建。 ( 注意:tearDown 放在了 finally{} 中,在测试异常抛出后仍会被执行到,因此它是被保证运行的。 )

  主体工作还是在 junit.framework.TestCase.runTest() 方法中 , 代码如下 :

  图 18. junit.framework.TestCase.runTest() 函数代码  

  该方法最根本的原理是:利用在图 13 中设定的 fName,借助 Reflection 机制,从 TestCase 中提取测试方法:

  runMethod = getClass().getMethod(fName, (Class[]) null);

  为每一个测试方法,创建一个方法对象 runMethod 并调用:

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

  只有在这里,用户测试方法的代码才开始被运行。

  在测试方法运行时,众多的 Assert 方法会根据测试的实际情况,抛出失败异常或者错误。也是在“ runMethod.invoke(this, (Object[]) new Class[0]); ”这里,这些异常或错误往上逐层抛出,或者被某一层次处理,或者处理后再次抛出,依次递推,最终显示给用户。

  流程图如下 :

  图 19. JUnit 执行测试方法,并在测试结束后将失败和错误信息通知所有 test listener  

0
相关文章