TestNG与JUnit

Junit源码阅读笔记三(TestClass)

2017-08-21  本文已影响27人  春狗

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

1.TestClass的结构

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

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);
}

由该构造方法不难发现

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参数会变为

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

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

上一篇下一篇

猜你喜欢

热点阅读