atic的。
如上,我们分析了BlockJUnit4ClassRunner的运行流程,也就是说当测试类为一个的时候JUnit是如何工作的。前文也提到过,ParentRunner还有一个子类Suite,表示需要运行一组测试,BlockJUnit4ClassRunner的一个运行单元为FrameworkMethod,而Suite的一个运行单元为Runner,我们看其runChild方法:
protected void runChild(Runner runner, final RunNotifier notifier) {
runner.run(notifier);
}
很是明了,直接滴啊用runner的run方法。这样,如果这个runner的实例仍然是Suite,则会继续向里运行,如果这个runner为BlockJUnit4ClassRunner,这执行我们前面分析的逻辑。这里有个问题是,那这个runner是如何生成的呢?这就要看Suite的构造函数:
protected Suite(Class<?> klass, Class<?>[] suiteClasses) throws InitializationError {
this(new AllDefaultPossibilitiesBuilder(true), klass, suiteClasses);
}
AllDefaultPossibilitiesBuilder的职责就是为每个类生找到对应的Runner,感兴趣可以查看其runnerForClass方法,比较容易理解,这里就不再赘述。
Matcher验证
上面我们分析了用@Test标注的函数是如何被JUnit执行的,但单单有@Test标注是肯定不够的,既然是测试,我们肯定需要一定的手段来验证程序的的执行是符合预期的。JUnit提供了Matcher机制,可以满足我们大部分的需求。Matcher相关类主要在org.hamcrest包下,先来看下类图:
上图仅仅列出了org.hamcrest包下的一部分类,这些类一起组合起来形成了JUnit强大的验证机制。
验证的基本写法是:
MatcherAssert.assertThat("saymagic", CoreMatchers.containsString("magic"));
首先我们需要调用的是MatcherAssert的assertThat方法,这个方法最终辗转为:
public static <T> void assertThat(String reason, T actual, Matcher<? super T> matcher) {
if (!matcher.matches(actual)) {
Description description = new StringDescription();
description.appendText(reason)
.appendText("\nExpected: ")
.appendDescriptionOf(matcher)
.appendText("\n but: ");
matcher.describeMismatch(actual, description);
throw new AssertionError(description.toString());
}
}
这个函数目的很是明确,直接判断matcher是否匹配,不匹配则封装描述信息,然后抛出异常。所以我们来关注matcher的matchs方法都做了些什么,CoreMatchers.containsString("magic")
返回的就是一个matcher, CoreMatchers相当于一个静态工厂,提供了大量的静态方法来返回各种Matcher:
我们就已刚刚的containsString为例,查看其内部代码:
public static org.hamcrest.Matcher<java.lang.String> containsString(java.lang.String substring) {
return org.hamcrest.core.StringContains.containsString(substring);
}
可见其调用了StringContains的一个静态方法,继续追:
@Factory
public static Matcher<String> containsString(String substring) {
return new StringContains(substring);
}
这里很简单,直接new了一个StringContains实例,StringContains的继承关系如下:
首先BaseMatcher实现了Matcher接口,TypeSafeMatcher是BaseMatcher的一个抽象实现,它的matches方法如下:
public final boolean matches(Object item) {
return item != null
&& expectedType.isInstance(item)
&& matchesSafely((T) item);
}
可见它在验证前作了判空与类型的校验,所以子类就可以实现matchesSafely方法,就无需在此方法中进行判空与类型的验证了。
SubstringMatchers是TypeSafeMatcher的一种实现,它是对字符串类验证的一种抽象,它的matchesSafely方法如下:
@Override
public boolean matchesSafely(String item) {
return eva lSubstringOf(item);
}
子类需要实现eva lSubstringOf方法。如此,我们就可以看下StringContains的这个方法了:
@Override
protected boolean eva lSubstringOf(String s) {
return s.indexOf(substring) >= 0;
}
出奇的简单,并没有什么好解释的。这个如果返回了false,说明验证不通过,前面的assertThat方法就会抛出异常。这样,JUnit的一个测试就不会通过。
assert翻译过来为断言,也就是说,它是用来验证是非的,但我们也清楚,并非所有的事情都分是非,测试也如此,比如我们要测试登录模块,当点击login按钮的时候,可能验证通过后就跳转了页面,并没有任何返回值,这个时候我们往往会验证某个事情发生了,比如login后执行了跳转方法,这样就表示测试是通过的。这就是Mock框架来做的是。感兴趣的可以查看我的上一篇文章Mockito源码解析
总结
读懂JUnit的源码并不是很困难,我相信这与整体架构设计得当有关,使人读起来神清气爽。 此文也仅仅是对JUnit的源码粗略概括,更多的细节还有待大家仔细琢磨。