美文网首页TestNG与JUnit
Junit源码阅读笔记三(TestClass)

Junit源码阅读笔记三(TestClass)

作者: 春狗 | 来源:发表于2017-08-21 01:08 被阅读27次

    前两篇记录了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
    

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

    相关文章

      网友评论

        本文标题:Junit源码阅读笔记三(TestClass)

        本文链接:https://www.haomeiwen.com/subject/sbbqdxtx.html