junit源码解析--测试驱动运行阶段
2016-02-01 17:25
417 查看
前面的博客里面我们已经整理了junit的初始化阶段,接下来就是junit的测试驱动运行阶段,也就是运行所有的testXXX方法。OK,现在我们开始吧。
前面初始化junit之后,开始执行doRun方法。
分析一下上面的代码,一共分3步。
1,初始化TestResult,然后往该对象上注册事件。
fPrinter 是 junit.textui.ResultPrinter 类的实例,该类提供了向控制台输出测试结果的一系列功能接口,输出的格式在类中定义。 ResultPrinter 类实现了 TestListener 接口,具体实现了 addError、addFailure、endTest 和 startTest 四个重要的方法,这种设计是 Observer 设计模式的体现,在 addListener 方法的代码中:
2,计时开始,运行测试用例,计时结束。
这块才是重点,OK,我们来仔细的分析下。前面说的junit38默认的测试执行器就是Test接口的一个实现类TestSuite。我们先来看下该类的run源码:
TestSuite。那假如我们提供了suite()静态方法,就会使用我们自己测试组件。
========================================================================================================================================================================================
比如下面的测试代码:
明显的这里我们自己创建的TestSuite中也包含了一个Suite了呢。我们还是来看下addTestSuite()这个方法的源码吧;
[b]========================================================================================================================================================================================================
[/b]
每次循环得到的节点 test,都带着我们在测试执行器中new()出来的TestResult,然后将result一起传递给 runTest 方法,进行下一步更深入的运行。那OK,现在我们在来看下每个测试用例的执行情况。下面贴出每个测试用例的run()方法源码:
这里的
startTest 和 endTest 方法也是 Observer 设计模式中的两个重要的消息更新方法。
========================================================================================================================================================================================================
我们先来看下startTest()方法就好了。
========================================================================================================================================================================================================
现在我们来看重点,test.runBare()。
这里变量 P 指向一个实现了 Protectable 接口的匿名类的实例,Protectable 接口只有一个 protect 待实现方法。而 junit.framework.TestResult.runProtected(Test, Protectable) 方法的定义为:
可见 runProtected 方法实际上是调用了刚刚实现的 protect 方法,也就是调用了 test.runBare() 方法。
在该方法中,最终的测试会传递给一个 runTest 方法执行,注意此处的 runTest 方法是无参的,注意与之前形似的方法区别。该方法中也出现了经典的 setUp 方法和 tearDown 方法,追溯代码可知它们的定义为空。用户可以覆盖两者,进行一些 fixture 的自定义和搭建。 ( 注意:tearDown 放在了 finally{} 中,在测试异常抛出后仍会被执行到,因此它是被保证运行的。 )
该方法最根本的原理是:利用在前面初始化测试用例时候设定的 fName,借助 Reflection 机制,从 TestCase 中提取测试方法:
JUnit 执行测试方法,并在测试结束后将失败和错误信息通知所有 test listener。
OK,至此真个junit的测试执行阶段结束了呢。下一篇整理最后下一个阶段,junit的结果捕获阶段。
前面初始化junit之后,开始执行doRun方法。
Test suite = getTest(testCase); return doRun(suite, wait);doRun()方法的代码如下:
/** * @创建时间: 2016年1月22日 * @相关参数: @param suite * @相关参数: @param wait * @相关参数: @return * @功能描述: 测试执行器执行测试 */ public TestResult doRun(Test suite, boolean wait) { TestResult result = createTestResult(); result.addListener(fPrinter); long startTime = System.currentTimeMillis(); suite.run(result); long endTime = System.currentTimeMillis(); long runTime = endTime - startTime; fPrinter.print(result, runTime); pause(wait); return result; }
分析一下上面的代码,一共分3步。
1,初始化TestResult,然后往该对象上注册事件。
fPrinter 是 junit.textui.ResultPrinter 类的实例,该类提供了向控制台输出测试结果的一系列功能接口,输出的格式在类中定义。 ResultPrinter 类实现了 TestListener 接口,具体实现了 addError、addFailure、endTest 和 startTest 四个重要的方法,这种设计是 Observer 设计模式的体现,在 addListener 方法的代码中:
/** * @创建时间: 2016年1月21日 * @相关参数: @param listener 测试用例监听 * @功能描述: 注册一个事件 */ public synchronized void addListener(TestListener listener) { fListeners.add(listener); }将 ResultPrinter 对象加入到 TestResult 对象的监听器列表中,因此实质上 TestResult 对象可以有多个监听器显示测试结果。下一篇博客junit--测试结果捕获阶段的分析中我将会描述对监听器的消息更新。这里有一个说明的就是,这个fPrinter是TestResult类的一个属性,在new TestResult的时候就被设值成System.out了。
2,计时开始,运行测试用例,计时结束。
这块才是重点,OK,我们来仔细的分析下。前面说的junit38默认的测试执行器就是Test接口的一个实现类TestSuite。我们先来看下该类的run源码:
/** * 运行测试 */ public void run(TestResult result) { for (Test each : fTests) { if (result.shouldStop()) { break; } runTest(each, result); } } public void runTest(Test test, TestResult result) { test.run(result); }Junit 通过 fore循环 对 TestSuite 中的整个“树结构”递归遍历运行其中的节点和叶子。此处 JUnit 代码颇具说服力地说明了 Composite 模式的效力,run 接口方法的抽象具有重大意义,它实现了客户代码与复杂对象容器结构的解耦,让对象容器自己来实现自身的复杂结构,从而使得客户代码就像处理简单对象一样来处理复杂的对象容器。上篇博客我们也说了,如果我们自己写的测试类提供suite()的静态方法,那么junit就自动帮我们创建一个运行组件new
TestSuite。那假如我们提供了suite()静态方法,就会使用我们自己测试组件。
========================================================================================================================================================================================
比如下面的测试代码:
public class LinkinTestAll extends TestCase { public static Test suite() { TestSuite suite = new TestSuite().addTestSuite(LinkinTest.class); suite.addTest(new LinkinTest1("testLinkin8Error")).addTest(new LinkinTest1("testLinkin4Normal")); return suite; } public static void main(String args[]) { TestRunner.run(suite()); } }
明显的这里我们自己创建的TestSuite中也包含了一个Suite了呢。我们还是来看下addTestSuite()这个方法的源码吧;
/** * @创建时间: 2016年1月22日 * @相关参数: @param testClass * @功能描述: 直接添加测试类到suite中 */ public TestSuite addTestSuite(Class<? extends TestCase> testClass) { addTest(new TestSuite(testClass)); return this; }在我们的测试类中如果TestSuite又包含了一个TestSuite,那么全面我们说的那个fore循环就开始发生作用了,如果测试用例类型是TestCase直接就被触发执行,如果是TestSuite那么就再自己掉一次进去自己包含的TestSuite中去执行其中的测试用例。
[b]========================================================================================================================================================================================================
[/b]
每次循环得到的节点 test,都带着我们在测试执行器中new()出来的TestResult,然后将result一起传递给 runTest 方法,进行下一步更深入的运行。那OK,现在我们在来看下每个测试用例的执行情况。下面贴出每个测试用例的run()方法源码:
public void run(TestResult result) { result.run(this); }下面贴出TestResult中的run()方法源码:
/** * @创建时间: 2016年1月21日 * @相关参数: @param test * @功能描述: 运行一个测试用例,命令者模式 */ protected void run(final TestCase test) { startTest(test); Protectable p = new Protectable() { public void protect() throws Throwable { test.runBare(); } }; runProtected(test, p); endTest(test); }明显的这里这里执行每一个测试用例,同样分为小3步,通知监听测试开始了-->真正去反射执行测试用例(重点)-->通知监听测试结束了呢。
这里的
startTest 和 endTest 方法也是 Observer 设计模式中的两个重要的消息更新方法。
========================================================================================================================================================================================================
我们先来看下startTest()方法就好了。
/** * @创建时间: 2016年1月21日 * @相关参数: @param test * @功能描述: 通知结果:测试用例可以开始执行了 */ public void startTest(Test test) { final int count = test.countTestCases(); synchronized (this) { fRunTests += count; } for (TestListener each : cloneListeners()) { System.out.println("###########开始迭代运行整套测试,互相独立###########"); each.startTest(test); } }
========================================================================================================================================================================================================
现在我们来看重点,test.runBare()。
这里变量 P 指向一个实现了 Protectable 接口的匿名类的实例,Protectable 接口只有一个 protect 待实现方法。而 junit.framework.TestResult.runProtected(Test, Protectable) 方法的定义为:
/** * @创建时间: 2016年1月21日 * @相关参数: @param test * @相关参数: @param p * @功能描述: 运行一个用例 */ 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() 方法。
public void runBare() throws Throwable { System.out.println("~~~~~~~~~~~~~~~~~~~~~~~"); System.out.println("第二步:框架开始运行测试===="); Throwable exception = null; setUp(); try { runTest(); } catch (Throwable running) { exception = running; } finally { try { tearDown(); System.out.println("第三步:框架结束运行测试===="); System.out.println("~~~~~~~~~~~~~~~~~~~~~~~"); } catch (Throwable tearingDown) { if (exception == null) exception = tearingDown; } } if (exception != null) throw exception; } /** * @创建时间: 2016年1月21日 * @相关参数: @throws Exception * @功能描述: 测试前准备 */ protected void setUp() throws Exception { System.out.println("====框架执行默认的setUp===="); } /** * @创建时间: 2016年1月21日 * @相关参数: @throws Exception * @功能描述: 测试后操作 */ protected void tearDown() throws Exception { System.out.println("====框架执行默认的tearDown===="); } protected void runTest() throws Throwable { assertNotNull("测试的方法名不能为空", fName); Method runMethod = null; try { runMethod = getClass().getMethod(fName, (Class[]) null); } catch (NoSuchMethodException e) { fail("Method \"" + fName + "\" not found"); } if (!Modifier.isPublic(runMethod.getModifiers())) { fail("Method \"" + fName + "\" should be public"); } try { System.out.println(String.format("框架开始执行测试,执行的方法是-->%s", runMethod)); runMethod.invoke(this); System.out.println(String.format("框架结束执行测试,执行的方法是-->%s", runMethod)); } catch (InvocationTargetException e) { e.fillInStackTrace(); throw e.getTargetException(); } catch (IllegalAccessException e) { e.fillInStackTrace(); throw e; } }
在该方法中,最终的测试会传递给一个 runTest 方法执行,注意此处的 runTest 方法是无参的,注意与之前形似的方法区别。该方法中也出现了经典的 setUp 方法和 tearDown 方法,追溯代码可知它们的定义为空。用户可以覆盖两者,进行一些 fixture 的自定义和搭建。 ( 注意:tearDown 放在了 finally{} 中,在测试异常抛出后仍会被执行到,因此它是被保证运行的。 )
该方法最根本的原理是:利用在前面初始化测试用例时候设定的 fName,借助 Reflection 机制,从 TestCase 中提取测试方法:
runMethod = getClass().getMethod(fName, (Class[]) null);为每一个测试方法,创建一个方法对象 runMethod 并调用:
runMethod.invoke(this);只有在这里,用户测试方法的代码才开始被运行。在测试方法运行时,众多的 Assert 方法会根据测试的实际情况,抛出失败异常或者错误。也是在上行代码这里,这些异常或错误往上逐层抛出,或者被某一层次处理,或者处理后再次抛出,依次递推,最终显示给用户。
JUnit 执行测试方法,并在测试结束后将失败和错误信息通知所有 test listener。
OK,至此真个junit的测试执行阶段结束了呢。下一篇整理最后下一个阶段,junit的结果捕获阶段。
相关文章推荐
- Fix The code of method _jspService(HttpServletRequest, HttpServletResponse) is exceeding the 65535 b
- junit源码解析--测试驱动运行阶段
- json中jobject
- MySQL中行锁的算法
- 粒子系统制作流动效果
- java最常用的设计模式之一外观模式
- 大话设计模式-备忘录模式
- 合成/聚合复用原则
- java向上转型和向下转型
- Python 10.3 base64
- ORACLE系统函数之---环境变量查询
- 详解Spring事件驱动模型
- 高效地分析Android内存--MAT工具解析
- SwipeRefreshLayout 与 CoordinatorLayout 嵌套刷新
- 接口测试总结
- 7.8 Models -- The Rest Adapter
- 基于Pipe的PureMVC FLEX框架的多核共享消息技术
- js功能实现的特效--距离新年还有多少天
- lua实现单例模式
- 软件架构设计