Junit源碼閱讀筆記三(TestClass)

前兩篇記錄了Junit入口主流程,以及Runner的構(gòu)建,接下來(lái)看一下用來(lái)描述我們測(cè)試類的類-TestClass

1.TestClass的結(jié)構(gòu)

Junit把我們的測(cè)試類都抽象為一個(gè)TestClass,測(cè)試類中的每一個(gè)屬性都抽象為FrameworkField,每一個(gè)抽象方法抽象為FrameworkMethod,而FrameworkFieldFrameworkMethod都繼承自FrameworkMember
換言之,我們測(cè)試類與Junit中對(duì)應(yīng)的描述為

  • 測(cè)試類對(duì)應(yīng)TestClass
  • 測(cè)試類中所有方法對(duì)應(yīng)FrameworkMethod
  • 測(cè)試類中所有屬性對(duì)應(yīng)FrameworkField
  • FrameworkFieldFrameworkMethod統(tǒng)一標(biāo)識(shí)為FrameworkMember(后續(xù)會(huì)講為什么要這么抽象)
    接下來(lái)看一下類圖
TestClass

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ò)RunnerTestClass構(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

這種葉子組合的模式就是組合模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,506評(píng)論 19 139
  • 1.Runner 上一節(jié)講到了Junit的運(yùn)行實(shí)際上是調(diào)用Runner中的run方法執(zhí)行的,那么接下來(lái)總結(jié)一下Ru...
    春狗閱讀 1,351評(píng)論 0 4
  • JUnit Intro Android基于JUnit Framework來(lái)書(shū)寫(xiě)測(cè)試代碼。JUnit是基于Java語(yǔ)...
    chandarlee閱讀 2,387評(píng)論 0 50
  • 1.寫(xiě)在前面 基于junit 4.12版本,對(duì)junit源碼閱讀之后的理解和總結(jié),如有不正確的地方,請(qǐng)多指正 2....
    春狗閱讀 2,552評(píng)論 0 8
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,255評(píng)論 6 342

友情鏈接更多精彩內(nèi)容