Junit源码阅读笔记三(TestClass)

前两篇记录了Junit入口主流程,以及Runner的构建,接下来看一下用来描述我们测试类的类-TestClass

1.TestClass的结构

Junit把我们的测试类都抽象为一个TestClass,测试类中的每一个属性都抽象为FrameworkField,每一个抽象方法抽象为FrameworkMethod,而FrameworkFieldFrameworkMethod都继承自FrameworkMember
换言之,我们测试类与Junit中对应的描述为

  • 测试类对应TestClass
  • 测试类中所有方法对应FrameworkMethod
  • 测试类中所有属性对应FrameworkField
  • FrameworkFieldFrameworkMethod统一标识为FrameworkMember(后续会讲为什么要这么抽象)
    接下来看一下类图
TestClass

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.总结

通过RunnerTestClass构建你会发现,Junit中所有测试类,都会被一个TestClass来描述,而且会有一个Runner与之对应,负责测试的运行,而所有的Runner又都会被Suite这个Runner包裹着,结构如下

Suite |- Runner1 -- TestClass
      |- Runner2 -- TestClass
      |- Runner3 -- TestClass

这种叶子组合的模式就是组合模式

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,265评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,078评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,852评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,408评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,445评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,772评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,921评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,688评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,130评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,467评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,617评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,276评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,882评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,740评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,967评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,315评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,486评论 2 348

推荐阅读更多精彩内容