• Junit4 架构设计系列(1): Request,ClassRequest 和 RunnerBuilder


    Overall##

    Junit的成功已不言而喻,其广泛应用于单元测试,测试驱动开发领域。大量的工具,IDE都集成了JUnit,著名的有Maven,Ant,Eclipse,甚至像Google SDK提供的Android Testing Framework也是基于JUnit。

    Junit的成功离不开其优美的架构设计,本着知其然,更要知其所以然的宗旨,希望用这个系列来记录下自己的对Junit4设计的理解。

    如有不对或不足的地方,欢迎各位指正!

    Junit4 的入口在哪?##

    要想研究Junit4的源码,首先我们要知道Junit4的入口在哪里。
    JUnitCore类是Facade模式的典型使用,它提供Static的方法去执行各种类型的Case, 包括JUnit 4 tests 和JUnit 3.8.x tests。设置命令行下,我们也可以调用这个JunitCore去执行Case.
    很明显他就是这个Junit4的入口之一。

    但是其他工具是如何继承Junit4的呢?###

    以Maven为例,看看Maven是如何和Junit4集成的:

    private static void execute( Class<?> testClass, RunNotifier notifier, Filter filter )
    {
        final int classModifiers = testClass.getModifiers();
    
        // filter.shouldRunClass( testClass )
        if ( !Modifier.isAbstract( classModifiers ) && !Modifier.isInterface( classModifiers ) )
        {
            Runner runner = Request.aClass( testClass ).filterWith( filter ).getRunner();
            if ( countTestsInRunner( runner.getDescription() ) != 0 )
            {
                runner.run( notifier );
            }
        }
    }
    

    (如需访问完整代码,点击这里)

    其实如果我们跟进去JunitCore的runClasses方法,会发现,这Maven用的方式跟JunitCore的方式大同小异,其基本执行路劲都是 Request->Runer->runner.run()

    所以要分析Junit4,我们得看看这三步是怎么走的。

    Request 类##

    Request类表示Case执行请求的抽象概念。也就是说当我们想让Junit4执行我们的Case时,实际上我们发送的就是这样一个请求。
    Request允许执行单个case,也支持一次处理多条case,分别有不同的方法来Handle。

    这里要注意的是Request是Abstract类,它有个Adstract方法:

    /**
     * Returns a Runner for this Request
     *
     * @return corresponding Runner for this Request
     */
    public abstract Runner getRunner();
    

    这个方法很重要,从注释我们看到,它是想获得相应的能够处理我们期望Request的Runner。

    那么这个Runner类到底是怎么实现的呢?

    ClassRequest##

    ClassRequest是Request的派生类,它实现了getRunner()抽象方法。

    @Override
    public Runner getRunner() {
        if (runner == null) {
            synchronized (runnerLock) {
                if (runner == null) {
                    runner = new AllDefaultPossibilitiesBuilder(canUseSuiteMethod).safeRunnerForClass(fTestClass);
                }
            }
        }
        return runner;
    }
    

    ClassRequest类比较直白,就是调用AllDefaultPossibilitiesBuilder类的safeRunnerForClass 方法去获得合适的Runner类。

    再仔细看它的实现,这不就是采用双重检查加锁的单例模式嘛?!不过不是为了获得ClassRequest类的实例,而是为了获得合适的Runner。

    AllDefaultPossibilitiesBuilder##

    AllDefaultPossibilitiesBuilder类使用了Builder模式,safeRunnerForClass根据传入的类,来确定要用什么样的Runner来执行这条Case,不同的Runner适用于不同类型的Case

    @Override
    public Runner runnerForClass(Class<?> testClass) throws Throwable {
        List<RunnerBuilder> builders = Arrays.asList(
                ignoredBuilder(),
                annotatedBuilder(),
                suiteMethodBuilder(),
                junit3Builder(),
                junit4Builder());
    
        for (RunnerBuilder each : builders) {
            Runner runner = each.safeRunnerForClass(testClass);
            if (runner != null) {
                return runner;
            }
        }
        return null;
    }
    

    而List中的ignoredBuilder,annotatedBuilder,suiteMethodBuilder,junit3Builder,和junit4Builder 都是RunnerBuilder类的子类,各自都实现了safeRunnerForClass方法,最终这些类自己会判断自己是否适合执行这些Case,如实合适就实例化相应的Runer并返回。

    而Runner类最终调用Runner.run()方法来执行Case。

    是不是很巧妙?

    Junit4 High Level 类图##

    下面是Junit4 High Level 调用顺序的类图,也算是对上面的一个总结:

    从上面我们不难看出Junit4的调用路径:
    Request -> ClassRequest.getRunner() -> AllDefaultPossibilitiesBuilder.safeRunnerForClass() -> runner.run()

    设计模式知识补充##

    Builder模式###

    Builder模式又叫生成器模式,是一种对象构建模式,它把复杂对象的构建过程抽象出来,使这个抽象过程能够根据不同方式,构造出不同的对象。
    维基百科的例子可以用下面的类图表示:

    相比较而言,个人觉得Junit4的Builder模式运用的更巧妙些,AllDefaultPossibilitiesBuilder类本身既是RunnerBuilder的派生类,但是其也使用初始化RunnerBuilder类的所有派生类。

    单例模式###

    单例模式确保一个类只有一个实例,并提供一个全局访问点。
    单例模式的写法很多,但是各种写法各有利弊:

    • 经典实现 缺点就是线程不安全
    • 方法级加Synchronized 性能有损失,因为方法只有在第一次执行时,才需要同步
    • 利用Static特性,JVM加载时初始化对象 也有缺点,因为如果对象比较大时,一直Hold住这个对象比较耗内存,起不到按需初始化的要求
    • 双重检查加锁这种方式还不错,上文说到ClassRequest的getRunner()方法就是使用这个
    • 内部类方法方式这种方式比较少见,但是很巧妙,使用静态式的成员内部类,该内部类只有在被调用时才会被JVM装载,从而实现了延迟加载

    更多具体实现可以参见汤姆大叔的博客

    敬请期待##

    后续计划

    童鞋,如果觉得本文还算用心,还算有用,何不点个赞呢(⊙o⊙)?

  • 相关阅读:
    laravel获取不到session
    laravel表单提交419错误
    'cross-env' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
    centos 虚拟机出问题 Oh no,something has gone wrong! 解决方法
    fastadmin关闭验证码登录
    php二维数组排序
    不自动显示html表单记住的内容 自动完成等清除记忆
    两个服务器之间使用minio同步文件
    redis获取数据库个数
    html跳转页面
  • 原文地址:https://www.cnblogs.com/jinsdu/p/4646895.html
Copyright © 2020-2023  润新知