JUnit是由 Erich Gamma 和 Kent Beck 编写的一个回归测试框架,以Eclipse、IDEA等为代表的Java开发环境都对JUnit提供了非常友善的支持。提到Erich Gamma,他就是大名鼎鼎的
《设计模式:可复用面向对象软件的基础》
一书的作者之一。因此,JUnit当中的设计模式的运用相当得当,所以,JUnit的源码可谓相当优良的一本武林秘籍,非常值得一看。 本文基于JUnit4.12,将从JUnit的运行流程,Match验证,两个方面,来对JUnit的源码进行整体的分析。
运行流程
JUnit的启动方式有很多,比如在Android Studio中我们可以直接点击某个被@Test注解的函数来运行:
此时,启动的是JUniteStarter,该类是intellij为我们提供的。感兴趣可以查看其源码: https://github.com/JetBrains/intellij-community/blob/master/plugins/junit_rt/src/com/intellij/rt/execution/junit/JUnitStarter.java
如果我们使用gradle, 可以执行gradle test
运行测试,实际上是在一个线程中执行SuiteTestClassProcessor的processTestClass方法来进行启动。其源码可以查看https://github.com/gradle/gradle/blob/master/subprojects/testing-base/src/main/java/org/gradle/api/internal/tasks/testing/SuiteTestClassProcessor.java
如上两种都是第三方工具为我们提供的便捷方式,实际上JUnit也提供了一个名为JUnitCore
的类来供我们方便的运行测试用例。
尽管启动JUnit的方式有很多,但这都是打开与JUnit对话的一些方式,最终执行的还是JUnit当中的起到核心作用的一些类,为了让大家对这些核心boss有一个初步了解,我画了一个类图:
上图中仅是JUnit中的几个核心的类,也是本分主要分析的对象。这里先给出一些对象的职责,可以有个大体的了解,后面会通过代码就会更清楚每个对象是如何完成这些职责的:
- 在类图的中央,有个叫做ParentRunne的对象很引人注目,它继承自Runner.
- Runner则表示着JUnit对整个测试的抽象
- Runner实现了Describable接口,Describable接口中唯一的函数
getDescription()
返回了Description对象,记录着测试的信息。 - Statement 是一个抽象类,其 eva luate()函数代表着在测试中将被执行的方法。
- 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),