前兩篇記錄了Junit入口主流程,以及Runner的構(gòu)建,接下來(lái)看一下用來(lái)描述我們測(cè)試類的類-TestClass
1.TestClass的結(jié)構(gòu)
Junit把我們的測(cè)試類都抽象為一個(gè)TestClass,測(cè)試類中的每一個(gè)屬性都抽象為FrameworkField,每一個(gè)抽象方法抽象為FrameworkMethod,而FrameworkField和FrameworkMethod都繼承自FrameworkMember
換言之,我們測(cè)試類與Junit中對(duì)應(yīng)的描述為
- 測(cè)試類對(duì)應(yīng)
TestClass - 測(cè)試類中所有方法對(duì)應(yīng)
FrameworkMethod - 測(cè)試類中所有屬性對(duì)應(yīng)
FrameworkField -
FrameworkField和FrameworkMethod統(tǒng)一標(biāo)識(shí)為FrameworkMember(后續(xù)會(huì)講為什么要這么抽象)
接下來(lái)看一下類圖

2.TestClass的構(gòu)建
TestClass是何時(shí)構(gòu)建的呢?答案就在Runner的構(gòu)建中
讓我們?cè)倩氐?code>Suite的構(gòu)建過(guò)程中,在構(gòu)建Suite時(shí)會(huì)先把測(cè)試類對(duì)應(yīng)的Runner都構(gòu)建好,如果沒(méi)有特殊聲明,那么我們的測(cè)試類默認(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);
//校驗(yàn)
validate();
}
我們主要看createTestClass方法
protected TestClass createTestClass(Class<?> testClass) {
//很簡(jiǎn)單,只有一行new操作
return new TestClass(testClass);
}
讓我們繼續(xù)看TestClass的構(gòu)建過(guò)程都做了什么事
public TestClass(Class<?> clazz) {
this.clazz = clazz;
//檢驗(yàn)測(cè)試類是否有多參的構(gòu)造方法
if (clazz != null && clazz.getConstructors().length > 1) {
throw new IllegalArgumentException(
"Test class can only have one constructor");
}
//創(chuàng)建以Annotation為key,以測(cè)試方法為value的map
Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations =
new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>();
// //創(chuàng)建以Annotation為key,以測(cè)試類中屬性為value的map
Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations =
new LinkedHashMap<Class<? extends Annotation>, List<FrameworkField>>();
//掃描測(cè)試類中所有補(bǔ)注解過(guò)的屬性和方法
//并將結(jié)果填充到以上兩個(gè)集合中
scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations);
this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations);
this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations);
}
由該構(gòu)造方法不難發(fā)現(xiàn)
Junit把所有被注解過(guò)的方法存入以方法注解為key,以方法為value的map中- 把所有被注解過(guò)的屬性放入以屬性注解為key,以屬性為value的map中
3.測(cè)試類中屬性和方法和掃描
進(jìn)入scanAnnotatedMembers方法
protected void scanAnnotatedMembers(Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations, Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations) {
//遍歷測(cè)試類,及測(cè)試類所有父類
for (Class<?> eachClass : getSuperClasses(clazz)) {
//處理測(cè)試類中的每一個(gè)測(cè)試方法,在處理前先進(jìn)行排序
for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) {
addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations);
}
//處理測(cè)試類中的屬性,處理前也會(huì)進(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);
}
}
}
從該段代碼中,你會(huì)發(fā)現(xiàn)處理測(cè)試方法和測(cè)試類屬性時(shí),首先將方法和屬性分別包裝為FrameworkMethod``FrameworkField,然后調(diào)用的都是同一個(gè)方法addToAnnotationLists,再看該方法的方法簽名protected static <T extends FrameworkMember<T>> void addToAnnotationLists(T member, Map<Class<? extends Annotation>, List<T>> map)
該方法的參數(shù)正是FrameworkMember的子類,正是由于該抽象,才使得在解析測(cè)試類方法和屬性時(shí)都做同樣的處理(因?yàn)樘幚矸绞揭粯?
好了,繼續(xù)看addToAnnotationLists
protected static <T extends FrameworkMember<T>> void addToAnnotationLists(T member,
Map<Class<? extends Annotation>, List<T>> map) {
//遍歷測(cè)試方法或者屬性上的注解
for (Annotation each : member.getAnnotations()) {
Class<? extends Annotation> type = each.annotationType();
List<T> members = getAnnotatedMembers(map, type, true);
//如果集合中已經(jīng)有該測(cè)試方法或者屬性,不再處理
if (member.isShadowedBy(members)) {
return;
}
//將同一注解下所有方法或者屬性添加到List中
//如果有Before或者BeforeClass,將會(huì)放在List中的第一個(gè)元素位置
if (runsTopToBottom(type)) {
members.add(0, member);
} else {
members.add(member);
}
}
}
舉個(gè)例子,假如我們有個(gè)測(cè)試類
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)過(guò)該方法處理后 addToAnnotationLists方法上的map參數(shù)會(huì)變?yōu)?/p>
- key:@Test
- value:[test1,test2]
該類比較簡(jiǎn)單,里面涉及到的排序等細(xì)節(jié)不再詳述
4.Suite與TestClass
在Suite 這個(gè)總Runner構(gòu)建時(shí),也會(huì)調(diào)用父類ParentRunner構(gòu)建方法來(lái)構(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)來(lái),該方法調(diào)用的地方為上一個(gè)構(gòu)造方法
public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError {
this(null, builder.runners(null, classes));
}
該klass參數(shù)為null,也就是說(shuō),在構(gòu)建Suite時(shí),會(huì)創(chuàng)建一個(gè)與之對(duì)應(yīng)的TestClass,只不過(guò)這個(gè)TeshClass里的屬性都是空的
5.總結(jié)
通過(guò)Runner及TestClass構(gòu)建你會(huì)發(fā)現(xiàn),Junit中所有測(cè)試類,都會(huì)被一個(gè)TestClass來(lái)描述,而且會(huì)有一個(gè)Runner與之對(duì)應(yīng),負(fù)責(zé)測(cè)試的運(yùn)行,而所有的Runner又都會(huì)被Suite這個(gè)Runner包裹著,結(jié)構(gòu)如下
Suite |- Runner1 -- TestClass
|- Runner2 -- TestClass
|- Runner3 -- TestClass
這種葉子組合的模式就是組合模式