Junit源码阅读笔记三(TestClass)
前两篇记录了Junit
入口主流程,以及Runner
的构建,接下来看一下用来描述我们测试类的类-TestClass
1.TestClass的结构
Junit
把我们的测试类都抽象为一个TestClass
,测试类中的每一个属性都抽象为FrameworkField
,每一个抽象方法抽象为FrameworkMethod
,而FrameworkField
和FrameworkMethod
都继承自FrameworkMember
换言之,我们测试类与Junit
中对应的描述为
- 测试类对应
TestClass
- 测试类中所有方法对应
FrameworkMethod
- 测试类中所有属性对应
FrameworkField
-
FrameworkField
和FrameworkMethod
统一标识为FrameworkMember
(后续会讲为什么要这么抽象)
接下来看一下类图
2.TestClass的构建
TestClass
是何时构建的呢?答案就在Runner
的构建中
让我们再回到Suite
的构建过程中,在构建Suite
时会先把测试类对应的Runner
都构建好,如果没有特殊声明,那么我们的测试类默认使用的是BlockJUnit4ClassRunner
,看下该类的构造方法
public BlockJUnit4ClassRunner(Class<?> klass) throws InitializationError {
super(klass);
}
进入super(klass)
,看下父类ParentRunner
的构造方法里做了什么事
protected ParentRunner(Class<?> testClass) throws InitializationError {
//创建TestClass
this.testClass = createTestClass(testClass);
//校验
validate();
}
我们主要看createTestClass
方法
protected TestClass createTestClass(Class<?> testClass) {
//很简单,只有一行new操作
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");
}
//创建以Annotation为key,以测试方法为value的map
Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations =
new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>();
// //创建以Annotation为key,以测试类中属性为value的map
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);
}
由该构造方法不难发现
Junit
把所有被注解过的方法存入以方法注解为key,以方法为value的map中- 把所有被注解过的属性放入以属性注解为key,以属性为value的map中
3.测试类中属性和方法和扫描
进入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);
}
}
}
从该段代码中,你会发现处理测试方法和测试类属性时,首先将方法和属性分别包装为FrameworkMethod``FrameworkField
,然后调用的都是同一个方法addToAnnotationLists
,再看该方法的方法签名protected static <T extends FrameworkMember<T>> void addToAnnotationLists(T member, Map<Class<? extends Annotation>, List<T>> map)
该方法的参数正是FrameworkMember
的子类,正是由于该抽象,才使得在解析测试类方法和属性时都做同样的处理(因为处理方式一样)
好了,继续看addToAnnotationLists
protected static <T extends FrameworkMember<T>> void addToAnnotationLists(T member,
Map<Class<? extends Annotation>, List<T>> map) {
//遍历测试方法或者属性上的注解
for (Annotation each : member.getAnnotations()) {
Class<? extends Annotation> type = each.annotationType();
List<T> members = getAnnotatedMembers(map, type, true);
//如果集合中已经有该测试方法或者属性,不再处理
if (member.isShadowedBy(members)) {
return;
}
//将同一注解下所有方法或者属性添加到List中
//如果有Before或者BeforeClass,将会放在List中的第一个元素位置
if (runsTopToBottom(type)) {
members.add(0, member);
} else {
members.add(member);
}
}
}
举个例子,假如我们有个测试类
MyTest {
@Test
public void test1() {
System.out.print("this is a test");
}
@Test
public void test2() {
System.out.print("this is a test");
}
}
经过该方法处理后 addToAnnotationLists
方法上的map
参数会变为
- key:@Test
- value:[test1,test2]
该类比较简单,里面涉及到的排序等细节不再详述
4.Suite与TestClass
在Suite
这个总Runner
构建时,也会调用父类ParentRunner
构建方法来构造TestClass
protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError {
//调用父类构造方法
super(klass);
this.runners = Collections.unmodifiableList(runners);
}
但是从JunitCore
跟进来,该方法调用的地方为上一个构造方法
public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError {
this(null, builder.runners(null, classes));
}
该klass
参数为null
,也就是说,在构建Suite
时,会创建一个与之对应的TestClass
,只不过这个TeshClass
里的属性都是空的
5.总结
通过Runner
及TestClass
构建你会发现,Junit
中所有测试类,都会被一个TestClass
来描述,而且会有一个Runner
与之对应,负责测试的运行,而所有的Runner
又都会被Suite
这个Runner
包裹着,结构如下
Suite |- Runner1 -- TestClass
|- Runner2 -- TestClass
|- Runner3 -- TestClass
这种叶子组合的模式就是组合模式