junit源码解析--测试驱动运行阶段
前面的博客里面我们已经整理了junit的初始化阶段,接下来就是junit的测试驱动运行阶段,也就是运行所有的testXXX方法。OK,现在我们开始吧。
前面初始化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中去执行其中的测试用例。
========================================================================================================================================================================================================
每次循环得到的节点 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 设计模式中的两个重要的消息更新方法。
========================================================================================================================================================================================================
/**
* @创建时间: 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()。
/**
* @创建时间: 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源码解析--测试驱动运行阶段的更多相关文章
- junit源码解析总结
前面的博客我们也已经整理到了,我们使用junit38,在写测试类的时候我们的测试类必须继承TestCase.这个所有测试类的父类在junit.framework包下面. 前面我们的整理都是说直接在ID ...
- junit源码解析--核心类
JUnit 的概念及用途 JUnit 是由 Erich Gamma 和 Kent Beck 编写的一个开源的单元测试框架.它属于白盒测试,只要将待测类继承 TestCase 类,就可以利用 JUnit ...
- junit源码解析--捕获测试结果
OK,前面的博客我们整理了junit运行完了所有的测试用例,那么OK了,现在开始该收集测试结果了. 在这最后一步中,junit主要是玩一个类,TestResult.这里类中封装了几个参数,在初始化这个 ...
- junit源码解析--初始化阶段
OK,我们接着上篇整理.上篇博客中已经列出的junit的几个核心的类,这里我们开始整理junit完整的生命周期. JUnit 的完整生命周期分为 3 个阶段:初始化阶段.运行阶段和结果捕捉阶段. 这篇 ...
- MySQL的JDBC驱动源码解析
原文: MySQL的JDBC驱动源码解析 大家都知道JDBC是Java访问数据库的一套规范,具体访问数据库的细节有各个数据库厂商自己实现 Java数据库连接(JDBC)由一组用 Java 编程语言 ...
- 机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理、源码解析及测试
机器学习实战(Machine Learning in Action)学习笔记————03.决策树原理.源码解析及测试 关键字:决策树.python.源码解析.测试作者:米仓山下时间:2018-10-2 ...
- Tensorflow版Faster RCNN源码解析(TFFRCNN) (2)推断(测试)过程不使用RPN时代码运行流程
本blog为github上CharlesShang/TFFRCNN版源码解析系列代码笔记第二篇 推断(测试)过程不使用RPN时代码运行流程 作者:Jiang Wu 原文见:https://hom ...
- [源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器
[源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器 目录 [源码解析] PyTorch 分布式(15) --- 使用分布式 RPC 框架实现参数服务器 0x0 ...
- 【Spring源码分析】.properties文件读取及占位符${...}替换源码解析
前言 我们在开发中常遇到一种场景,Bean里面有一些参数是比较固定的,这种时候通常会采用配置的方式,将这些参数配置在.properties文件中,然后在Bean实例化的时候通过Spring将这些.pr ...
随机推荐
- Android开发——使用高级的RecyclerView实现侧滑菜单删除功能(SwipeRecyclerView)
使用之前,先简单介绍一下这个SwipeRecyclerView,这是严大(严振杰)基于RecyclerView的进行修改和封装的高级RecyclerView,其可以实现像QQ聊天界面的侧滑删除菜单,和 ...
- Java—javac Hello.java找不到文件
刚开始编写Java代码时,会遇到很多困难,下面来说一个比较常见的错误,如下: 对于初学者,一般都是从Hello,World开始的学起的,废了好大劲儿,铜鼓了半天,终于要在DOS上运行javac Hel ...
- mysql之数据操作
一 介绍 MySQL数据操作: DML 在MySQL管理软件中,可以通过SQL语句中的DML语言来实现数据的操作,包括 使用INSERT实现数据的插入 UPDATE实现数据的更新 使用DELETE实现 ...
- 购物车css样式效果
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 自定义注解,andjdk提供的元注解
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface FruitN ...
- Java_web学习(一) jdk配置
1.下载好jdk1.8.0版本或以上版本 2.配置JAVA_HOME,CLASSPATH,PATH 其中JAVA_HOME必须的 2.1 JAVA_HOME=E:\java\jdk1.8.0_77 ...
- ICP在pose-graph中的作用
从1维的角度进行解释: 1m P0 o-----------------------o P1 假如LOAM计算的两个车辆初始位姿P0.P1如上图所示,作为pose-graph的两个顶点.那么二 ...
- bzoj:1675 [Usaco2005 Feb]Rigging the Bovine Election 竞选划区
Description It's election time. The farm is partitioned into a 5x5 grid of cow locations, each of wh ...
- linux系统下Vi编辑器或者Vim编辑器设置显示行号、自动缩进、调整tab键宽度的技巧?
工作中嫌vim 中一个tab键的宽度太大,linux系统默认,没改之前是一个tab键宽度是8个字符,想改成4个字符, 操作如下:(注意:这是在root用户下)cd ~vim .vimrc添加如下几行: ...
- Windows 部署 Redis 群集(转)
1,下载Redis for windows 的最新版本,解压到 c:\Redis 目录下备用https://github.com/MSOpenTech/redis/releases当前我使用的是 3. ...