Junit源码阅读笔记二(Runner构建)

1.Runner

上一节讲到了Junit的运行实际上是调用Runner中的run方法执行的,那么接下来总结一下Runner,首先我们看下Runner的类图

Runner

2.Runner的构建

让我们回到类Request.class中的classes方法

public static Request classes(Computer computer, Class<?>... classes) {
    try {
        AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
        Runner suite = computer.getSuite(builder, classes);
        return runner(suite);
    } catch (InitializationError e) {
        throw new RuntimeException(
                "Bug in saff's brain: Suite constructor, called as above, should always complete");
    }
}

由此可知第一个Runner是由computer.getSuite(builder, classes)中获得,参数builder是直接new AllDefaultPossibilitiesBuilder,从名称可以看出,这个builder是所有默认的可能builder,看起来很牛逼的样子,点进去可以发现构造方法只有一个属性赋值,先忽略,继续看computer.getSuite(builder, classes)

public Runner getSuite(final RunnerBuilder builder,
        Class<?>[] classes) throws InitializationError {

    return new Suite(new RunnerBuilder() {
        @Override
        public Runner runnerForClass(Class<?> testClass) throws Throwable {
            return getRunner(builder, testClass);
        }
    }, classes);
}

protected Runner getRunner(RunnerBuilder builder, Class<?> testClass) throws Throwable {
    return builder.runnerForClass(testClass);
}

由此可见这个Runner是直接new的一个Suite,进入 Suite类看该处调用的构造方法,

public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError {
    this(null, builder.runners(null, classes));
}
protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError {
    super(klass);
    this.runners = Collections.unmodifiableList(runners);
}
  • 可见最终是调用Suiteprotected Suite(Class<?> klass, List<Runner> runners)构造方法构建的Suite

  • 该参数中的List<Runner> runners是由builder.runners而来,而由前而的调用链可知,这个builder是在Computer类中getSuite的方法中,在new Suite时直接new的一个匿名的RunnerBuilder

  • RunnerBuilder实现了runnerForClass方法,而该方法的具体实现是由上文(Requestclasses方法)中AllDefaultPossibilitiesBuilder来实现的,该处又是一个钩子方法,即当调用这个Suite对象的runnerForClass方法时,实际调用的是AllDefaultPossibilitiesBuilderrunnerForClass

  • 让我们回到Suite的构建中,由刚才的构造方法可以看出,在构建Suite之前先调用了匿名RunnerBuilder实例中的runners,继续看这个RunnerBuilder.runners都做了什么

      public List<Runner> runners(Class<?> parent, Class<?>[] children)
          throws InitializationError {
          //添加父class,此时由Suite调用过来时为null
          addParent(parent);
          try {
              //此时的children是从开始一路传递下来的测试类的class
              return runners(children);
          } finally {
              //移除父class
              removeParent(parent);
          }
      }
    

继续看该方法中调用的runners方法

 private List<Runner> runners(Class<?>[] children) {
    ArrayList<Runner> runners = new ArrayList<Runner>();
    for (Class<?> each : children) {
        //循环遍历所有测试类的class,构建Runner
        //由此可以发现每个测试类对应一个Runner
        Runner childRunner = safeRunnerForClass(each);
        if (childRunner != null) {
            runners.add(childRunner);
        }
    }
    return runners;
}

继续看safeRunnerForClass方法

public Runner safeRunnerForClass(Class<?> testClass) {
    try {
        return runnerForClass(testClass);
    } catch (Throwable e) {
        return new ErrorReportingRunner(testClass, e);
    }
}
  • 该方法实际是调用的runnerForClass来得到的Runner,再看下方法所在类:抽象类RunnerBuilder方法中
  • 还记得我们现在是哪个环节中嘛,正是在new Suite的构建方法中,所以这个地方的玄机就在于,该处的RunnerBuilder是前创建new Suite时创建的匿名RunnerBuilder,而这个匿名RunnerBuilderrunnerForClass正是由Request.classes方法中newAllDefaultPossibilitiesBuilder对象来实现的。
    这就是钩子方法的妙用,也称为模板方法
    接下来就让我们看下AllDefaultPossibilitiesBuilderrunnerForClass
    让我们来看下调用时序
Suite创建时Builder的调用

3.RunnerBuilder

首先来看上文中调用的AllDefaultPossibilitiesBuilder.runnerForClass

public Runner runnerForClass(Class<?> testClass) throws Throwable {
            //初始化RunnerBuilder列表
            //该处将Junit所有的RunnerBuilder都创建好放入集合
            //从该处可以以知道该类为什么叫AllDefaultPossibilitiesBuilder了

    List<RunnerBuilder> builders= Arrays.asList(
            ignoredBuilder(),  //IgnoredBuilder需要忽略测试的RunnerBuilder
            annotatedBuilder(),  //AnnotatedBuilder带有注解的RunnerBuilder
            suiteMethodBuilder(),  //SuiteMethodBuilder
            junit3Builder(),  //JUnit3Builder(目测是为了兼容junit3)
            junit4Builder());  //JUnit4Builder

    for (RunnerBuilder each : builders) {
        Runner runner= each.safeRunnerForClass(testClass);
        if (runner != null)
            return runner;
    }
    return null;
}
  • 由以上代码可以发现,在AllDefaultPossibilitiesBuilder中,首先把所有RunnerBuilder都构建好
  • 然后循环遍历,调用每一个buildersafeRunnerForClass方法
  • 进入该方法不难发现实际是调用每一个builder的runnerForClass方法,只要命中任何一个builder的构建规则,即使用该builder创建Rnner,然后退出循环
  • 该处应用正是一个责任链模式

让我们逐个看看各个builderrunnerForClass

  1. IgnoredBuilder:

     public Runner runnerForClass(Class<?> testClass) {
         if (testClass.getAnnotation(Ignore.class) != null)
             return new IgnoredClassRunner(testClass);
         }
         return null;
     }
    

可见如果测试类中加上了@Ignore则会使用该builder

  1. AnnotatedBuilder

     public Runner runnerForClass(Class<?> testClass) throws Exception {
         //获取测试类中用RunWith注解标注的类
         RunWith annotation= testClass.getAnnotation(RunWith.class);
         if (annotation != null)
             return buildRunner(annotation.value(), testClass);
         return null;
      }
    
     public Runner buildRunner(Class<? extends Runner> runnerClass,
         Class<?> testClass) throws Exception {
         try {
             ////创建RunWith注解中声明类的实例,并将测试类的class传入
             return runnerClass.getConstructor(Class.class).newInstance(
                 new Object[] { testClass });
         } catch (NoSuchMethodException e) {
             //先忽略异常处理
     }
    
  • 由该段代码可知,如果测试中在@RunWith注解中指定了Runner,则使用该builder,并使用反射创建指定的Runner
  • 看到此,相信你知道为什么我们在用Junit写单测的时候,单测类上面注解@RunWith的用途了
  1. SuiteMethodBuilder
    进入suiteMethodBuilder()方法

     protected RunnerBuilder suiteMethodBuilder() {
         if (fCanUseSuiteMethod)
             return new SuiteMethodBuilder();
         return new NullBuilder();
     }
    

首先我们看到的是不是直接new SuiteMethodBuilder,而是首先看fCanUseSuiteMethod属性是否为true,还记得该属性是什么时候赋值的嘛?返回去看下Request.classes方法

public static Request classes(Computer computer, Class<?>... classes) {
    try {
        AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
        Runner suite = computer.getSuite(builder, classes);
        return runner(suite);
    } catch (InitializationError e) {
        throw new RuntimeException(
                "Bug in saff's brain: Suite constructor, called as above, should always complete");
    }
}

正是new AllDefaultPossibilitiesBuilder(true); 中的true
也就是说,测试类中如果没有ignoredannotated,首先使用的RunnerBuilder实际是SuiteMethodBuilder,并执行该builderrunnerForClass方法,进入一探究竟

public Runner runnerForClass(Class<?> each) throws Throwable {
    //判断测试的class中是否有suite方法
    if (hasSuiteMethod(each))
        //创建suiteMethod,该类继承自JUnit38ClassRunner
        return new SuiteMethod(each);
    return null;
}

public boolean hasSuiteMethod(Class<?> testClass) {
    try {
        testClass.getMethod("suite");
    } catch (NoSuchMethodException e) {
        return false;
    }
    return true;
}
  • 根据此段代码可以发现,如果测试类中没有suite方法的话返回的是null
  • 此时在 AllDefaultPossibilitiesBuilder.runnerForClass中会继续循环寻找下一下builder
  1. JUnit3Builder:如果测试类使用的是Junit3的测试方式,则使用该builder,�该builder不再细看

  2. JUnit4Builder:该builder很简单,直接new BlockJUnit4ClassRunner

     public Runner runnerForClass(Class<?> testClass) throws Throwable {
         return new BlockJUnit4ClassRunner(testClass);
     }
    
  • 从以上AllDefaultPossibilitiesBuilder.runnerForClass方法的执行看来,Junit4默认使用的builderBlockJUnit4ClassRunner

让我们再回到我们的主线new Suite中,

public Suite(RunnerBuilder builder, Class<?>[] classes) throws InitializationError {
    this(null, builder.runners(null, classes));
}
protected Suite(Class<?> klass, List<Runner> runners) throws InitializationError {
    super(klass);
    //Collections.unmodifiableList将传入的List变得不可修改
    this.runners = Collections.unmodifiableList(runners);
}
  • 由以上流程可知,在创建Suite这个Runner时,首先把所有测试类对应的具体Runner通过对应的RunnerBuilder构建好,放入SuiteList<Runner> runners属性中,至此Suite构建完成(构造方法中的super(klass)后续再看)

总之,JunitCore中使用的Runner是直接newSuite,而这个Suite中属性List<Runner> runners,默认情况下,这些runner都是BlockJUnit4ClassRunner
至此Runner的构建完成,让我来接下来看下整个Runner构建的时序图

4.Runner构建时序图

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

推荐阅读更多精彩内容