junit4X系列--Builder、Request与JUnitCore
原文出处:http://www.blogjava.net/DLevin/archive/2012/05/12/377957.html。感谢作者的无私分享。
初次用文字的方式记录读源码的过程,不知道怎么写,感觉有点贴代码的嫌疑。不过中间还是加入了一些自己的理解和心得,希望以后能够慢慢的改进,感兴趣的童鞋凑合着看吧,感觉JUnit这个框架还是值得看的,里面有许多不错的设计思想在,更何况它是Kent Beck和Erich Gamma这样的大师写的。。。。。
深入JUnit源码之Builder、Request与JUnitCore
经过前面三节的Runner、Statement、Rule的讲解,事实上JUnit的核心运行逻辑已经完成了,剩下的就是一些外围的支持代码,包括Runner的构建、用Assert对测试方法的运行结果的验证代码以及为了兼容性而存在的一些代码。本节将关注Runner的构建部分,在JUnit中通过Request和RunnerBuilder共同支持。
在JUnit中,RunnerBuilder对根据测试类创建Runner逻辑的封装,特别是它支持@RunWith注解以在测试类中指定需要的执行的Runner,这也是自定义的Runner可以很方便的插入JUnit框架运行的原因,这个设计其实也蛮具有参考价值的:通过注解的方式为用户提供插入点,以扩展框架本身的功能;而在实现工程中,通过外围Builder来支持,从而不影响核心设计。
Request有点类似配置实例的感觉,用户可以根据自己的需求来定制Runner创建的信息,如代理给RunnerBuilder为每个测试类创建相应的Runner,根据用户的需求以决定是否要有filter和sort操作等。用户可以定义自己的Request实例,并传递给JUnitCore以实现自己特定的需求。
在现实中有可能存在这样的一个需求,即用户想同时运行多个测试类,而且这些测试类之间又是相互独立的,在JUnit中,使用Suite来表达这个需求,但是在Request中并没有一个单独的Request接收多个Class实例以创建Suite这个Runner,而是它使用了一个独立的类Computer来完成这样的功能,在Request中定义一个静态方法来处理这个问题。不过我很奇怪为什么要这么做,这个设计和现存的编程模型是格格不入的,个人不太赞同。
大体介绍了RunnerBuilder和Request的用途和设计,接下来将详细介绍他们的源码实现,先从RunnerBuilder开始。
RunnerBuilder
RunnerBuilder的核心就是根据给出的Class实例,创建相应的Runner。一般创建Runner遵循的逻辑是:
1. 如果Class中有@Ignored注解,那么将创建IgnoredClassRunner,该Runner在运行时什么都不做,只是触发testIgnored事件。
2. 如果Class中有@RunWith注解,则使用@RunWith注解中指定的Runner。
3. 如果Class中有静态suite()方法的存在,则使用SuiteMethod这个Runner(兼容JUnit3)。
4. 如果Class继承了TestCase类,则使用JUnit38ClassRunner(兼容JUnit3)。
5. 否则,使用BlockJUnit4ClassRunner(JUnit4中默认的Runner)。
其实这个逻辑就是AllDefaultPossibilitiesBuilder中构造Runner的实现。在JUnit内部大量的使用这个RunnerBuilder来构造Runner。其源码如下:
2 private final boolean fCanUseSuiteMethod;
3 public AllDefaultPossibilitiesBuilder(boolean canUseSuiteMethod) {
4 fCanUseSuiteMethod= canUseSuiteMethod;
5 }
6 @Override
7 public Runner runnerForClass(Class<?> testClass) throws Throwable {
8 List<RunnerBuilder> builders= Arrays.asList(
9 ignoredBuilder(),
annotatedBuilder(),
suiteMethodBuilder(),
junit3Builder(),
junit4Builder());
for (RunnerBuilder each : builders) {
Runner runner= each.safeRunnerForClass(testClass);
if (runner != null)
return runner;
}
return null;
}
protected JUnit4Builder junit4Builder() {
return new JUnit4Builder();
}
protected JUnit3Builder junit3Builder() {
return new JUnit3Builder();
}
protected AnnotatedBuilder annotatedBuilder() {
return new AnnotatedBuilder(this);
}
protected IgnoredBuilder ignoredBuilder() {
return new IgnoredBuilder();
}
protected RunnerBuilder suiteMethodBuilder() {
if (fCanUseSuiteMethod)
return new SuiteMethodBuilder();
return new NullBuilder();
}
}
其中fCanUseSuiteMethod用于表达测试类中静态的suite()方法是否被视为用于获得多个实例运行的方法,这个是为了兼容JUnit3而存在,而且在JUnit内部的使用时一般都是给true。再加上junit3Builder()放在junit4Builder()之前构造RunnerBuilder,表明为了兼容JUnit3,JUnit4以JUnit3中的风格为首选风格。
这里将不对为了兼容JUnit3而创建的RunnerBuilder做介绍,因而下面只会介绍IgnoredBuilder、AnnotatedBuilder、JUnit4Builder,事实上它都太简单了,以至于基本上不用什么介绍了。IgnoredBuilder会检查传入的Class实例是否有@Ignored注解,若有,则创建IgnoredClassRunner,否则返回null;AnnotatedBuilder检查传入的Class实例是否有@RunWith注解,若有,则使用@RunWith注解中指定的Runner,否则,返回null,这里需要注意的是在用户自定义的Runner中,必须包含一个以Class实例作为参数的构造函数,或者以Class实例和RunnerBuilder实例作为参数的构造函数,否则在构造自定义的Runner时会出错;JUnit4Builder直接根据传入的测试类Class的实例创建BlockJUnit4ClassRunner。
2 @Override
3 public Runner runnerForClass(Class<?> testClass) {
4 if (testClass.getAnnotation(Ignore.class) != null)
5 return new IgnoredClassRunner(testClass);
6 return null;
7 }
8 }
9 public class AnnotatedBuilder extends RunnerBuilder {
private static final String CONSTRUCTOR_ERROR_FORMAT= "Custom runner class %s should have a public constructor with signature %s(Class testClass)";
private RunnerBuilder fSuiteBuilder;
public AnnotatedBuilder(RunnerBuilder suiteBuilder) {
fSuiteBuilder= suiteBuilder;
}
@Override
public Runner runnerForClass(Class<?> testClass) throws Exception {
RunWith annotation= testClass.getAnnotation(RunWith.class);
if (annotation != null)
return buildRunner(annotation.value(), testClass);
return null;
}
public Runner buildRunner(Class<? extends Runner> runnerClass,
Class<?> testClass) throws Exception {
try {
return runnerClass.getConstructor(Class.class).newInstance(
new Object[] { testClass });
} catch (NoSuchMethodException e) {
try {
return runnerClass.getConstructor(Class.class,
RunnerBuilder.class).newInstance(
new Object[] { testClass, fSuiteBuilder });
} catch (NoSuchMethodException e2) {
String simpleName= runnerClass.getSimpleName();
throw new InitializationError(String.format(
CONSTRUCTOR_ERROR_FORMAT, simpleName, simpleName));
}
}
}
}
public class JUnit4Builder extends RunnerBuilder {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
return new BlockJUnit4ClassRunner(testClass);
}
}
而RunBuilder类本身也定义了一些方法,以帮助其他Runner,如Suite,构建其内部通过其他方式取到的测试类Class实例。这里对parent字段的存在有必要解释一下,因为我刚开始看到的时候也很费解,addParent()方法只在一个方法中调用一次,而且就这个类来看也不存在递归,为什么会有对相同parents的验证?要解释这个问题,需要知道Suite的构造函数还会调用runners()方法,加入有一次调用parent为一个使用Suite的类,这个类同时又在children中出现,那么在调用该方法使将给类加入到parents中,而后在构造children中的该类时又会调用该方法,将想用的Class实例加入parents中,从而引起异常。
2 private final Set<Class<?>> parents= new HashSet<Class<?>>();
3 public abstract Runner runnerForClass(Class<?> testClass) throws Throwable;
4 public Runner safeRunnerForClass(Class<?> testClass) {
5 try {
6 return runnerForClass(testClass);
7 } catch (Throwable e) {
8 return new ErrorReportingRunner(testClass, e);
9 }
}
Class<?> addParent(Class<?> parent) throws InitializationError {
if (!parents.add(parent))
throw new InitializationError(String.format("class '%s' (possibly indirectly) contains itself as a SuiteClass", parent.getName()));
return parent;
}
void removeParent(Class<?> klass) {
parents.remove(klass);
}
public List<Runner> runners(Class<?> parent, Class<?>[] children)
throws InitializationError {
addParent(parent);
try {
return runners(children);
} finally {
removeParent(parent);
}
}
public List<Runner> runners(Class<?> parent, List<Class<?>> children)
throws InitializationError {
]));
}
private List<Runner> runners(Class<?>[] children) {
ArrayList<Runner> runners= new ArrayList<Runner>();
for (Class<?> each : children) {
Runner childRunner= safeRunnerForClass(each);
if (childRunner != null)
runners.add(childRunner);
}
return runners;
}
}
最后来看一下RunnerBuilder的类结构图吧,了解一下目前存在的几个RunnerBuilder。
Request
Request是对RunnerBuilder的封装,它提供了改变RunnerBuilder创建出的Runner的接口,如创建Runner后,用Filter或Sorter过滤或重新排列测试方法的顺序。就目前JUnit只有Filter和Sorter可以对Runner做一些自定义的配置。Filter可以定义那些测试方法是可以运行的,比如在eclipse中提供的对一个测试方法单独运行就是使用它来实现;或者用户可以自己定义一个可以运行方法的集合,然后只要遇到这样的方法,然后根据这个集合来编写自定义的Filter。Sorter则用于排列Runner内部测试方法的执行顺序,但是这个定制只是对一个Runner中的测试方法有用,它并不会排列跨Runner之间的测试方法。不废话了,先来看一下Request的类结构图吧。
Request的结构比较简单,而且代码实现也比较简单,Request是一个抽象类,它定义了一个getRunner()的抽象方法,这个方法只是返回一个Runner实例。其中ClassRequest根据一个测试类,使用AllDefaultPossibilitiesBuilder创建一个Runner;FilterRequest则以一个Request和Filter实例为构造参数,在实现getRunner()方法时,根据传入的Request获取Runner,并对改Runner应用传入的Filter以过滤掉那些不需要运行的测试方法;SortingRequest也是以一个Request和Comparator<Description>为构造参数,在实现getRunner()方法是,根据传入的Request获取Runner,并根据comparator构造Sorter对刚获取到的Runner排序。这些实现有点Decorator模式的味道。
2 private final Class<?> fTestClass;
3 private boolean fCanUseSuiteMethod;
4 public ClassRequest(Class<?> testClass, boolean canUseSuiteMethod) {
5 fTestClass= testClass;
6 fCanUseSuiteMethod= canUseSuiteMethod;
7 }
8 public ClassRequest(Class<?> testClass) {
9 this(testClass, true);
}
@Override
public Runner getRunner() {
return new AllDefaultPossibilitiesBuilder(fCanUseSuiteMethod).safeRunnerForClass(fTestClass);
}
}
public final class FilterRequest extends Request {
private final Request fRequest;
private final Filter fFilter;
public FilterRequest(Request classRequest, Filter filter) {
fRequest= classRequest;
fFilter= filter;
}
@Override
public Runner getRunner() {
try {
Runner runner= fRequest.getRunner();
fFilter.apply(runner);
return runner;
} catch (NoTestsRemainException e) {
return new ErrorReportingRunner(Filter.class, new Exception(String
.format("No tests found matching %s from %s", fFilter
.describe(), fRequest.toString())));
}
}
}
public class SortingRequest extends Request {
private final Request fRequest;
private final Comparator<Description> fComparator;
public SortingRequest(Request request, Comparator<Description> comparator) {
fRequest= request;
fComparator= comparator;
}
@Override
public Runner getRunner() {
Runner runner= fRequest.getRunner();
new Sorter(fComparator).apply(runner);
return runner;
}
}
public abstract class Request {
public abstract Runner getRunner();
}
除了Request类结构,Request类本身还提供了多个工场方法,以一种不需要知道Request类结构的方法创建Request,也算是一种封装吧,使用起来比较方便,而且随着框架的演化,可以添加或删除子类而不需要考虑用户是否使用了某个子类。如果做的安全一些、然后不考虑测试的话,可以把FilterRequest和SortingRequest的可见性降低,如包级别的。除了一些静态的工场方法,Request为Filter和Sorter也提供了各自的方法支持,在我们得到一个Request的引用后,只需要调用这两个方法即可构造需要的Request(FilterRequest或SortingRequest)。
2 public static Request method(Class<?> clazz, String methodName) {
3 Description method= Description.createTestDescription(clazz, methodName);
4 return Request.aClass(clazz).filterWith(method);
5 }
6 public static Request aClass(Class<?> clazz) {
7 return new ClassRequest(clazz);
8 }
9 public static Request classWithoutSuiteMethod(Class<?> clazz) {
return new ClassRequest(clazz, false);
}
public static Request classes(Computer computer, Class<?>

try {
AllDefaultPossibilitiesBuilder builder= new AllDefaultPossibilitiesBuilder(true);
Runner suite= computer.getSuite(builder, classes);
return runner(suite);
} catch (InitializationError e) {
throw new RuntimeException(
"Bug in saff's brain: Suite constructor, called as above, should always complete");
}
}
public static Request classes(Class<?>

return classes(JUnitCore.defaultComputer(), classes);
}
public static Request runner(final Runner runner) {
return new Request(){
@Override
public Runner getRunner() {
return runner;
}
};
}
public Request filterWith(Filter filter) {
return new FilterRequest(this, filter);
}
public Request filterWith(final Description desiredDescription) {
return filterWith(Filter.matchMethodDescription(desiredDescription));
}
public Request sortWith(Comparator<Description> comparator) {
return new SortingRequest(this, comparator);
}
}
public class Computer {
public static Computer serial() {
return new Computer();
}
public Runner getSuite(final RunnerBuilder builder,
Class<?>[] classes) throws InitializationError {
return new Suite(new RunnerBuilder() {
@Override
public Runner runnerForClass(Class<?> testClass) throws Throwable {
return getRunner(builder, testClass);
}
}, classes);
}
protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable {
return builder.runnerForClass(testClass);
}
}
这里对Computer这个类引入的意义一直没有弄明白,为什么不直接在Request.classes()方法中创建Suite?即使要提取到Computer中以给创建Runner提供扩展点,直接在getSuite()方法中使用builder创建Suite就可以了啊,但是又要将getRunner()方法提取出来,这个提取可以给子类在根据builder和testClass创建Runner提供扩展点,但是以我目前的水平,还是看不多这个扩展点存在的意义。
JUnitCore
JUnitCore是JUnit中运行Request的门面类,同时它也提供了对命令模式的测试实现,它接收多个测试类作为参数,然后运行这些测试类中的所有测试方法。其实现是从传入的参数中取到所有的测试类的Class实例,然后根据这些测试类的Class实例创建Request实例,从创建的Request实例中可以取得Runner实例,运行该Runner,并处理事件逻辑,最后如果所有测试通过,则退出值为0,否则为1。为了统计测试结果信息,JUnit还提供了一个默认的RunListener实现:TextRunListener,这个Listener在每个测试方法开始的时候打印一个点’.’,当一个测试方法失败是打印E,当一个测试方法被忽略时打印I,当所有测试方法执行完成后打印总体统计时间,如运行时间、所有错误信息的异常堆栈以及最后成功多少、失败多少等信息。对于基于JUnit编写更适合项目本身的测试运行的用户来说,最重要的就是几个run()方法,这些用户可以通过实现自己特定的逻辑以创建出符合自己需求的Request或通过某种方式查找到所有自己要运行的测试类等,然后调用你需要的run()方法。
2 private RunNotifier fNotifier;
3 public JUnitCore() {
4 fNotifier= new RunNotifier();
5 }
6 public static Result runClasses(Computer computer, Class<?>

7 return new JUnitCore().run(computer, classes);
8 }
9 public static Result runClasses(Class<?>

return new JUnitCore().run(defaultComputer(), classes);
}
public Result run(Class<?>

return run(Request.classes(defaultComputer(), classes));
}
public Result run(Computer computer, Class<?>

return run(Request.classes(computer, classes));
}
public Result run(Request request) {
return run(request.getRunner());
}
public Result run(Runner runner) {
Result result= new Result();
RunListener listener= result.createListener();
fNotifier.addFirstListener(listener);
try {
fNotifier.fireTestRunStarted(runner.getDescription());
runner.run(fNotifier);
fNotifier.fireTestRunFinished(result);
} finally {
removeListener(listener);
}
return result;
}
}
junit4X系列--Builder、Request与JUnitCore的更多相关文章
- junit4X系列--Runner解析
前面我整理了junit38系列的源码,那junit4X核心代码也基本类似.这里我先转载一些关于junit4X源码解析的好文章.感谢原作者的分享.原文地址:http://www.blogjava.net ...
- Junit4X系列--hamcrest的使用
OK,在前面的一系列博客里面,我整理过了Assert类下面常用的断言方法,比如assertEquals等等,但是org.junit.Assert类下还有一个方法也用来断言,而且更加强大.这就是我们这里 ...
- junit4X系列源码--总体介绍
原文出处:http://www.cnblogs.com/caoyuanzhanlang/p/3530267.html.感谢作者的无私分享. Junit是一个可编写重复测试的简单框架,是基于Xunit架 ...
- c++设计模式系列----builder模式
看了好几处关于builder模式的书和博客,总感觉不是很清楚,感觉不少书上的说的也不是很准确.最后还是看回圣经<设计模式>.看了好久终于感觉明白了一点了. 意图: builder模式提出的 ...
- Django学习系列之request对象
先来一个简单的实例 urls.py from django.conf.urls import url from django.contrib import admin from cmdb import ...
- junit4X系列--Assert与Hamcrest
原文出处:http://www.blogjava.net/DLevin/archive/2012/05/12/377960.html.感谢作者无私分享 到目前,JUnit4所有的核心源码都已经讲解过了 ...
- junit4X系列源码--Junit4 Runner以及test case执行顺序和源代码理解
原文出处:http://www.cnblogs.com/caoyuanzhanlang/p/3534846.html.感谢作者的无私分享. 前一篇文章我们总体介绍了Junit4的用法以及一些简单的测试 ...
- junit4X系列--Exception
原文出处:http://www.blogjava.net/DLevin/archive/2012/11/02/390684.html.感谢作者的无私分享. 说来惭愧,虽然之前已经看过JUnit的源码了 ...
- junit4X系列--Rule
原文出处:http://www.blogjava.net/DLevin/archive/2012/05/12/377955.html.感谢作者的无私分享. 初次用文字的方式记录读源码的过程,不知道怎么 ...
随机推荐
- PHP正在进行时-字符串动态插入变量
在PHP中,一般用双引号或者单引号将字符串括起来. echo "张三李四王五叫上赵六一起去'喝酒'". 如果要动态将将数据插入到字符串中,我们除了使用.号拼接,还可以通过使用{}来 ...
- 从Unity中的Attribute到AOP(七)
本章我们将依然讲解Unity中的Attribute,继续命名空间在UnityEngine里的. PropertyAttribute,这个特性主要来控制变量或者类在Inspector里面的显示方式.和P ...
- ThinkPHP3.2中英文切换!
小伙伴们好久不见!!! 最近公司项目版本升级,小梦已经忙成了狗,无暇顾及文章,今天抽时间写一篇助助兴! 用Thinkphp这个国产框架已经2年多了,现在有一个小功能:网站中英文切换功能,当然这 ...
- 在mac下使用终端命令通过ssh协议连接远程linux系统,代替windows的putty
指令:ssh username@server.address.com 事例:wangmingdeMacBook-Pro:~ xxxxxxxxxx$ ssh root@XXXX.net The auth ...
- 树莓派搭建WEB服务器
树莓派搭建WEB的教程网上有许多,但感觉每一篇都有一些问题,这次我将网上的教程汇总,并亲身实践,将注意的问题都写进去,方便新手学习! 目录:1,安装nginx+sqlite+php5打造轻量级服务器, ...
- CTF---密码学入门第七题 杯酒人生
杯酒人生分值:10 来源: Veneno 难度:易 参与人数:2633人 Get Flag:790人 答题人数:963人 解题通过率:82% 使用古典密码 一喵星人要想喵星发送一段不知道干什么用的密码 ...
- Link-Cut-Trees
填坑,填坑,填坑…… 开篇镇人品……下文的比喻仅供娱乐…… 为了迎接JSZX校内互测,我临时填坑学了LCT…… 怎么说呢……我也是懵懵懂懂地看了N篇博客,对着标程敲上一发代码,然后才慢慢理解.这里推荐 ...
- HDU 1013 Digital Roots【字符串,水】
Digital Roots Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 65536/32768 K (Java/Others)Tot ...
- [51nod1254]最大子段和 V2
N个整数组成的序列a[1],a[2],a[3],-,a[n],你可以对数组中的一对元素进行交换,并且交换后求a[1]至a[n]的最大子段和,所能得到的结果是所有交换中最大的.当所给的整数均为负数时和为 ...
- Effective Java 第三版——23. 优先使用类层次而不是标签类
Tips <Effective Java, Third Edition>一书英文版已经出版,这本书的第二版想必很多人都读过,号称Java四大名著之一,不过第二版2009年出版,到现在已经将 ...