关于junit:java开发框架之JUnit-学习分享

47次阅读

共计 10691 个字符,预计需要花费 27 分钟才能阅读完成。

运行流程
JUnit 的启动形式有很多,比方在 Android Studio 中咱们能够间接点击某个被 @Test 注解的函数来运行:

此时,启动的是 JUniteStarter,该类是 intellij 为咱们提供的。感 java 培训趣味能够查看其源码:
https://github.com/JetBrains/…
如果咱们应用 gradle,能够执行 gradle test 运行测试,实际上是在一个线程中执行 SuiteTestClassProcessor 的 processTestClass 办法来进行启动。其源码能够查看
https://github.com/gradle/gra…
如上两种都是第三方工具为咱们提供的便捷形式,实际上 JUnit 也提供了一个名为 JUnitCore 的类来供咱们不便的运行测试用例。
只管启动 JUnit 的形式有很多,但这都是关上与 JUnit 对话的一些形式,最终执行的还是 JUnit 当中的起到核心作用的一些类,为了让大家对这些外围 boss 有一个初步理解,我画了一个类图:

上图中仅是 JUnit 中的几个外围的类,也是本分次要剖析的对象。这里先给出一些对象的职责,能够有个大体的理解,前面会通过代码就会更分明每个对象是如何实现这些职责的:
• 在类图的地方,有个叫做 ParentRunne 的对象很引人注目,它继承自 Runner.
• Runner 则示意着 JUnit 对整个测试的形象
• Runner 实现了 Describable 接口,Describable 接口中惟一的函数 getDescription() 返回了 Description 对象,记录着测试的信息。
• Statement 是一个抽象类,其 evaluate()函数代表着在测试中将被执行的办法。
• ParentRunner 共有两个子类,BlockJUnit4ClassRunner 用来运行单个测试类,Suite 用来一起运行多个测试类
• RunnerBuilder 是生产 Runner 的策略,如应用 @RunWith(Suite.class) 标注的类须要应用 Suite,被 @Ignore 标注的类须要应用 IgnoreClassRunner。
• TestClass 是对被测试的类的封装

综上,咱们先从 ParentRunner 看起,其构造函数如下:
protected ParentRunner(Class<?> testClass) throws InitializationError {
this.testClass = createTestClass(testClass);
validate();
}
this.testClass 即前文所说的 TestClass,咱们进入 createTestClass 办法来查看其如何将 class 对象转换为 TestClass。
protected TestClass createTestClass(Class<?> testClass) {
return new TestClass(testClass);
}
并没什么货色,具体的逻辑都写在 TestClass 的外部:
public TestClass(Class<?> clazz) {
this.clazz = clazz;
if (clazz != null && clazz.getConstructors().length > 1) {
throw new IllegalArgumentException(
“Test class can only have one constructor”);
}
Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations =
new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>();
Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations =
new LinkedHashMap<Class<? extends Annotation>, List<FrameworkField>>();
scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations);
this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations);
this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations);
}
能够看到,整个构造函数大抵都在做一些验证和初始化的工作,须要引起咱们留神的应该是 scanAnnotatedMembers 办法:
protected void scanAnnotatedMembers(Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations, Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations) {
for (Class<?> eachClass : getSuperClasses(clazz)) {
for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) {
addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations);
}
// ensuring fields are sorted to make sure that entries are inserted
// and read from fieldForAnnotations in a deterministic order
for (Field eachField : getSortedDeclaredFields(eachClass)) {
addToAnnotationLists(new FrameworkField(eachField), fieldsForAnnotations);
}
}
}
整个函数的作用就是扫描 class 中办法和变量上的注解,并将其依据注解的类型进行分类,缓存在 methodsForAnnotations 与 fieldsForAnnotations 当中。须要留神的是,JUnit 对办法和变量别离封装为 FrameworkMethod 与 FrameworkField,它们都继承自 FrameworkMember,这样就为办法和变量进行了对立形象。
看完了 ParentRunner 的构造函数,咱们来看 ParentRunner 继承自 Runner 的 run 办法是如何工作的:
@Override
public void run(final RunNotifier notifier) {
EachTestNotifier testNotifier = new EachTestNotifier(notifier,
getDescription());
try {
Statement statement = classBlock(notifier);
statement.evaluate();
} catch (AssumptionViolatedException e) {
testNotifier.addFailedAssumption(e);
} catch (StoppedByUserException e) {
throw e;
} catch (Throwable e) {
testNotifier.addFailure(e);
}
}
其中比拟要害的代码是 classBlock 函数将 notifier 转换为 Statement:
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
}
return statement;
}
持续追进 childrenInvoker 之前,容许我当初这里先存个档,记为 A,一会咱们会回到 classBlock 这里
protected Statement childrenInvoker(final RunNotifier notifier) {
return new Statement() {
@Override
public void evaluate() {
runChildren(notifier);
}
};
}
childrenInvoker 返回的是一个 Statement,看它的 evaluate 办法,其调用的是 runChildren 办法,这也是 ParentRunner 中十分重要的一个函数:
private void runChildren(final RunNotifier notifier) {
final RunnerScheduler currentScheduler = scheduler;
try {
for (final T each : getFilteredChildren()) {
currentScheduler.schedule(new Runnable() {
public void run() {
ParentRunner.this.runChild(each, notifier);
}
});
}
} finally {
currentScheduler.finished();
}
}
这个函数就体现了形象的重要性,留神泛型 T,它在 ParentRunner 的每个实现类中各不相同,在 BlockJUnit4ClassRunner 中 T 示意 FrameworkMethod,具体到这个函数来讲 getFilteredChildren 拿到的是被 @Test 注解标注的 FrameworkMethod,而在 Suite 中,T 为 Runner,而 ParentRunner.this.runChild(each, notifier); 这句的中的 runChild(each, notifier)办法仍旧是个形象办法,咱们先看 BlockJUnit4ClassRunner 中的实现:
@Override
protected void runChild(final FrameworkMethod method, RunNotifier notifier) {
Description description = describeChild(method);
if (isIgnored(method)) {
notifier.fireTestIgnored(description);
} else {
runLeaf(methodBlock(method), description, notifier);
}
}
isIgnored 办法判断了 method 办法是否被 @Ignore 注解标识,如果是的话则间接告诉 notifier 触发 ignored 事件,否则,执行 runLeaf 办法, runLeaf 的第一个参数是 Statement,所以,BlockJUnit4ClassRunner 通过 methodBlock 办法将 method 转换为 Statement:
protected Statement methodBlock(FrameworkMethod method) {
Object test;
try {
test = new ReflectiveCallable() {
@Override
protected Object runReflectiveCall() throws Throwable {
return createTest();
}
}.run();
} catch (Throwable e) {
return new Fail(e);
}
Statement statement = methodInvoker(method, test);
statement = possiblyExpectingExceptions(method, test, statement);
statement = withPotentialTimeout(method, test, statement);
statement = withBefores(method, test, statement);
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
return statement;
}
后面的几行代码是在生成 test 对象,而 test 对象的类型则是咱们待测试的 class,接下来追进 methodInvoker 办法:
protected Statement methodInvoker(FrameworkMethod method, Object test) {
return new InvokeMethod(method, test);
}
可见,咱们生成的 Statement 实例为 InvokeMethod,咱们看下其 evaluate 办法:
testMethod.invokeExplosively(target);
invokeExplosively 函数做的事件就是对 target 对象调用 testMethod 办法。而后面咱们说过,这个 testMethod 在 BlockJUnit4ClassRunner 中就是被 @Test 所标注的办法,此时,咱们终于找到了 @Test 办法是在哪里被调用的了。别急,咱们接着方才的函数持续剖析:
statement = possiblyExpectingExceptions(method, test, statement);
statement = withPotentialTimeout(method, test, statement);
statement = withBefores(method, test, statement);
statement = withAfters(method, test, statement);
statement = withRules(method, test, statement);
咱们能够看到,statement 一直的在变形,而通过 withBefores,withRules 这些函数的名字咱们能够很容易猜到,这里就是在解决 @Before,@Rule 等注解的中央,咱们以 withBefores 为例:
protected Statement withBefores(FrameworkMethod method, Object target,
Statement statement) {
List<FrameworkMethod> befores = getTestClass().getAnnotatedMethods(
Before.class);
return befores.isEmpty() ? statement : new RunBefores(statement,
befores, target);
}
这个函数里首先拿到了所有被 @Before 标注的办法,将其封装为 RunBefores, 咱们看下其构造函数和
public RunBefores(Statement next, List<FrameworkMethod> befores, Object target) {
this.next = next;
this.befores = befores;
this.target = target;
}
public void evaluate() throws Throwable {
for (FrameworkMethod before : befores) {
before.invokeExplosively(target);
}
next.evaluate();
}
很是明了,evaluate 执行时,首先将 before 办法全副 invoke 来执行,而后才调用原始 statement 的 evaluate 办法。其余几个函数与此相似,感兴趣能够持续查看。
如此,咱们就明确了 runLeaf 办法的第一个参数 Statement 的由来,接下来就看下这个 runLeaf 办法做了什么,runLeaf 在 ParentRunner 中有默认的实现:
protected final void runLeaf(Statement statement, Description description,
RunNotifier notifier) {
EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
eachNotifier.fireTestStarted();
try {
statement.evaluate();
} catch (AssumptionViolatedException e) {
eachNotifier.addFailedAssumption(e);
} catch (Throwable e) {
eachNotifier.addFailure(e);
} finally {
eachNotifier.fireTestFinished();
}
}
非常简单,间接执行了 statement 的 evaluate 办法,须要留神的是这里的 statement 实例不肯定是什么了,有可能是 RunBefores,也有可能是 RunAfters,这就和被测试类中的注解无关了。
讲到这里,还记得后面咱们说过的存档 A 吗?咱们回到存档 A:
protected Statement classBlock(final RunNotifier notifier) {
Statement statement = childrenInvoker(notifier);
if (!areAllChildrenIgnored()) {
statement = withBeforeClasses(statement);
statement = withAfterClasses(statement);
statement = withClassRules(statement);
}
return statement;
}
刚刚存档后所产生的一起,其实就是在执行 Statement statement = childrenInvoker(notifier)这个代码。换句话说,childrenInvoker 的作用就是将所有须要执行的测试用例用一个 Statement 封装起来。进而点燃这个 Statement,就会触发所有的测试用例。但同样须要留神到被 if 语句突围的代码,咱们又看到了相熟的语句,Statement 还在被一直的转换,但此时是在类的层面,withBeforeClasses 函数操作的就是 @BeforeClass 注解了:
protected Statement withBeforeClasses(Statement statement) {
List<FrameworkMethod> befores = testClass
.getAnnotatedMethods(BeforeClass.class);
return befores.isEmpty() ? statement :
new RunBefores(statement, befores, null);
}
须要留神的是这回 RunBefores 的第三个参数为 null,阐明被 @BeforeClass 注解的办法只能是 static 的。
如上,咱们剖析了 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 evalSubstringOf(item);
}
子类须要实现 evalSubstringOf 办法。如此,咱们就可以看下 StringContains 的这个办法了:
@Override
protected boolean evalSubstringOf(String s) {
return s.indexOf(substring) >= 0;
}
出奇的简略,并没有什么好解释的。这个如果返回了 false,阐明验证不通过,后面的 assertThat 办法就会抛出异样。这样,JUnit 的一个测试就不会通过。
assert 翻译过去为断言,也就是说,它是用来验证是非的,但咱们也分明,并非所有的事件都分是非,测试也如此,比方咱们要测试登录模块,当点击 login 按钮的时候,可能验证通过后就跳转了页面,并没有任何返回值,这个时候咱们往往会验证某个事件产生了,比方 login 后执行了跳转办法,这样就示意测试是通过的。这就是 Mock 框架来做的是。
来自:saymagic

正文完
 0