前兩篇記錄了Junit
入口主流程垢油,以及Runner
的構(gòu)建芜繁,接下來看一下用來描述我們測試類的類-TestClass
1.TestClass的結(jié)構(gòu)
Junit
把我們的測試類都抽象為一個TestClass
俯邓,測試類中的每一個屬性都抽象為FrameworkField
甩恼,每一個抽象方法抽象為FrameworkMethod
贡羔,而FrameworkField
和FrameworkMethod
都繼承自FrameworkMember
換言之云稚,我們測試類與Junit
中對應(yīng)的描述為
- 測試類對應(yīng)
TestClass
- 測試類中所有方法對應(yīng)
FrameworkMethod
- 測試類中所有屬性對應(yīng)
FrameworkField
-
FrameworkField
和FrameworkMethod
統(tǒng)一標(biāo)識為FrameworkMember
(后續(xù)會講為什么要這么抽象)
接下來看一下類圖
2.TestClass的構(gòu)建
TestClass
是何時構(gòu)建的呢尸饺?答案就在Runner
的構(gòu)建中
讓我們再回到Suite
的構(gòu)建過程中进统,在構(gòu)建Suite
時會先把測試類對應(yīng)的Runner
都構(gòu)建好,如果沒有特殊聲明浪听,那么我們的測試類默認(rèn)使用的是BlockJUnit4ClassRunner
螟碎,看下該類的構(gòu)造方法
public BlockJUnit4ClassRunner(Class<?> klass) throws InitializationError {
super(klass);
}
進(jìn)入super(klass)
,看下父類ParentRunner
的構(gòu)造方法里做了什么事
protected ParentRunner(Class<?> testClass) throws InitializationError {
//創(chuàng)建TestClass
this.testClass = createTestClass(testClass);
//校驗
validate();
}
我們主要看createTestClass
方法
protected TestClass createTestClass(Class<?> testClass) {
//很簡單迹栓,只有一行new操作
return new TestClass(testClass);
}
讓我們繼續(xù)看TestClass
的構(gòu)建過程都做了什么事
public TestClass(Class<?> clazz) {
this.clazz = clazz;
//檢驗測試類是否有多參的構(gòu)造方法
if (clazz != null && clazz.getConstructors().length > 1) {
throw new IllegalArgumentException(
"Test class can only have one constructor");
}
//創(chuàng)建以Annotation為key掉分,以測試方法為value的map
Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations =
new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>();
// //創(chuàng)建以Annotation為key,以測試類中屬性為value的map
Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations =
new LinkedHashMap<Class<? extends Annotation>, List<FrameworkField>>();
//掃描測試類中所有補(bǔ)注解過的屬性和方法
//并將結(jié)果填充到以上兩個集合中
scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations);
this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations);
this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations);
}
由該構(gòu)造方法不難發(fā)現(xiàn)
Junit
把所有被注解過的方法存入以方法注解為key克伊,以方法為value的map中- 把所有被注解過的屬性放入以屬性注解為key酥郭,以屬性為value的map中
3.測試類中屬性和方法和掃描
進(jìn)入scanAnnotatedMembers
方法
protected void scanAnnotatedMembers(Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations, Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations) {
//遍歷測試類,及測試類所有父類
for (Class<?> eachClass : getSuperClasses(clazz)) {
//處理測試類中的每一個測試方法愿吹,在處理前先進(jìn)行排序
for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) {
addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations);
}
//處理測試類中的屬性不从,處理前也會進(jìn)行排序
// 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);
}
}
}
從該段代碼中,你會發(fā)現(xiàn)處理測試方法和測試類屬性時犁跪,首先將方法和屬性分別包裝為FrameworkMethod``FrameworkField
消返,然后調(diào)用的都是同一個方法addToAnnotationLists
载弄,再看該方法的方法簽名protected static <T extends FrameworkMember<T>> void addToAnnotationLists(T member, Map<Class<? extends Annotation>, List<T>> map)
該方法的參數(shù)正是FrameworkMember
的子類,正是由于該抽象撵颊,才使得在解析測試類方法和屬性時都做同樣的處理(因為處理方式一樣)
好了宇攻,繼續(xù)看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);
//如果集合中已經(jīng)有該測試方法或者屬性,不再處理
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");
}
}
經(jīng)過該方法處理后 addToAnnotationLists
方法上的map
參數(shù)會變?yōu)?/p>
- key:@Test
- value:[test1,test2]
該類比較簡單,里面涉及到的排序等細(xì)節(jié)不再詳述
4.Suite與TestClass
在Suite
這個總Runner
構(gòu)建時妻熊,也會調(diào)用父類ParentRunner
構(gòu)建方法來構(gòu)造TestClass
protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError {
//調(diào)用父類構(gòu)造方法
super(klass);
this.runners = Collections.unmodifiableList(runners);
}
但是從JunitCore
跟進(jìn)來夸浅,該方法調(diào)用的地方為上一個構(gòu)造方法
public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError {
this(null, builder.runners(null, classes));
}
該klass
參數(shù)為null
,也就是說扔役,在構(gòu)建Suite
時帆喇,會創(chuàng)建一個與之對應(yīng)的TestClass
,只不過這個TeshClass
里的屬性都是空的
5.總結(jié)
通過Runner
及TestClass
構(gòu)建你會發(fā)現(xiàn)亿胸,Junit
中所有測試類坯钦,都會被一個TestClass
來描述,而且會有一個Runner
與之對應(yīng)侈玄,負(fù)責(zé)測試的運行婉刀,而所有的Runner
又都會被Suite
這個Runner
包裹著,結(jié)構(gòu)如下
Suite |- Runner1 -- TestClass
|- Runner2 -- TestClass
|- Runner3 -- TestClass
這種葉子組合的模式就是組合模式