Junit源碼閱讀筆記三(TestClass)

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

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

Junit把我們的測試類都抽象為一個TestClass俯邓,測試類中的每一個屬性都抽象為FrameworkField甩恼,每一個抽象方法抽象為FrameworkMethod贡羔,而FrameworkFieldFrameworkMethod都繼承自FrameworkMember
換言之云稚,我們測試類與Junit中對應(yīng)的描述為

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

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é)

通過RunnerTestClass構(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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末序仙,一起剝皮案震驚了整個濱河市突颊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌潘悼,老刑警劉巖律秃,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異治唤,居然都是意外死亡友绝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門肝劲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迁客,“玉大人,你說我怎么就攤上這事辞槐≈朗” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵榄檬,是天一觀的道長卜范。 經(jīng)常有香客問我,道長鹿榜,這世上最難降的妖魔是什么海雪? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任锦爵,我火速辦了婚禮,結(jié)果婚禮上奥裸,老公的妹妹穿的比我還像新娘险掀。我一直安慰自己,他們只是感情好湾宙,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布樟氢。 她就那樣靜靜地躺著,像睡著了一般侠鳄。 火紅的嫁衣襯著肌膚如雪埠啃。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天伟恶,我揣著相機(jī)與錄音碴开,去河邊找鬼。 笑死博秫,一個胖子當(dāng)著我的面吹牛潦牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播台盯,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼畏线!你這毒婦竟也來了静盅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤寝殴,失蹤者是張志新(化名)和其女友劉穎蒿叠,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蚣常,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡市咽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了抵蚊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片施绎。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖贞绳,靈堂內(nèi)的尸體忽然破棺而出谷醉,到底是詐尸還是另有隱情,我是刑警寧澤冈闭,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響窘俺,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜矛绘,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望刃永。 院中可真熱鬧货矮,春花似錦、人聲如沸揽碘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽雳刺。三九已至劫灶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間掖桦,已是汗流浹背本昏。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留枪汪,地道東北人涌穆。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像雀久,于是被迫代替她去往敵國和親宿稀。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容