资讯专栏INFORMATION COLUMN

Junit源码阅读(四)之自定义扩展

Little_XM / 2139人阅读

摘要:前言上次的博客中我们着重介绍了的机制,这次我们将聚焦到自定义扩展上来。在很多情形下我们需要在测试过程中加入一些自定义的动作,这些就需要对进行包装,为此提供了以接口和为基础的扩展机制。

前言

上次的博客中我们着重介绍了Junit的Validator机制,这次我们将聚焦到自定义扩展Rule上来。在很多情形下我们需要在测试过程中加入一些自定义的动作,这些就需要对statement进行包装,Junit为此提供了以TestRule接口和RunRules为基础的Rule扩展机制。

基本模型

首选TestRule由注解@ClassRule来指定,下面我们先给出TestRule的定义。

public interface TestRule {
    /**
     * Modifies the method-running {@link Statement} to implement this
     * test-running rule.
     *
     * @param base The {@link Statement} to be modified
     * @param description A {@link Description} of the test implemented in {@code base}
     * @return a new statement, which may be the same as {@code base},
     *         a wrapper around {@code base}, or a completely new Statement.
     */
    Statement apply(Statement base, Description description);
}

代码中的注释十分清楚,TestRule提供结合Description为原Statement附加功能转变为新的Statement的apply方法。RunRules则是一系列TestRule作用后得到的Statement,如下:

public class RunRules extends Statement {
    private final Statement statement;

    public RunRules(Statement base, Iterable rules, Description description) {
        statement = applyAll(base, rules, description);
    }

    @Override
    public void evaluate() throws Throwable {
        statement.evaluate();
    }

    private static Statement applyAll(Statement result, Iterable rules,
            Description description) {
        for (TestRule each : rules) {
            result = each.apply(result, description);
        }
        return result;
    }
}
原理解释

那么RunRules又是如何在我们的测试运行过程中被转化的呢?还记得在第二篇博客中我们提到了在classBlock方法中statement会被withBeforeClasses等装饰,同样此处它也被withClassRules装饰。首先由testClass返回带@ClassRule注解的对应值,分别由getAnnotatedFieldValues和getAnnotatedMethodValues方法提供。之后我们将这些值转化为TestRule对象,然后将这个TestRule列表和原有的statement结合返回RunRules。

    private Statement withClassRules(Statement statement) {
        List classRules = classRules();
        return classRules.isEmpty() ? statement :
                new RunRules(statement, classRules, getDescription());
    }
TimeOut示例

接下来我们以超时扩展为示例来看一看一个扩展是如何起作用的。

public class Timeout implements TestRule {
    private final long timeout;
    private final TimeUnit timeUnit;
    private final boolean lookForStuckThread;

    
    public static Builder builder() {
        return new Builder();
    }

    
    @Deprecated
    public Timeout(int millis) {
        this(millis, TimeUnit.MILLISECONDS);
    }

    
    public Timeout(long timeout, TimeUnit timeUnit) {
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        lookForStuckThread = false;
    }

    
    protected Timeout(Builder builder) {
        timeout = builder.getTimeout();
        timeUnit = builder.getTimeUnit();
        lookForStuckThread = builder.getLookingForStuckThread();
    }

    
    public static Timeout millis(long millis) {
        return new Timeout(millis, TimeUnit.MILLISECONDS);
    }

    
    public static Timeout seconds(long seconds) {
        return new Timeout(seconds, TimeUnit.SECONDS);
    }

    
    protected final long getTimeout(TimeUnit unit) {
        return unit.convert(timeout, timeUnit);
    }

    
    protected final boolean getLookingForStuckThread() {
        return lookForStuckThread;
    }

    
    protected Statement createFailOnTimeoutStatement(
            Statement statement) throws Exception {
        return FailOnTimeout.builder()
            .withTimeout(timeout, timeUnit)
            .withLookingForStuckThread(lookForStuckThread)
            .build(statement);
    }

    public Statement apply(Statement base, Description description) {
        try {
            return createFailOnTimeoutStatement(base);
        } catch (final Exception e) {
            return new Statement() {
                @Override public void evaluate() throws Throwable {
                    throw new RuntimeException("Invalid parameters for Timeout", e);
                }
            };
        }
    }

    
    public static class Builder {
        private boolean lookForStuckThread = false;
        private long timeout = 0;
        private TimeUnit timeUnit = TimeUnit.SECONDS;

        protected Builder() {
        }

        
        public Builder withTimeout(long timeout, TimeUnit unit) {
            this.timeout = timeout;
            this.timeUnit = unit;
            return this;
        }

        protected long getTimeout() {
            return timeout;
        }

        protected TimeUnit getTimeUnit()  {
            return timeUnit;
        }

        
        public Builder withLookingForStuckThread(boolean enable) {
            this.lookForStuckThread = enable;
            return this;
        }

        protected boolean getLookingForStuckThread() {
            return lookForStuckThread;
        }


        /**
         * Builds a {@link Timeout} instance using the values in this builder.,
         */
        public Timeout build() {
            return new Timeout(this);
        }
    }
}

我们可以看到上述最核心的就是createFailOnTimeoutStatement方法,它直接返回了一个FailOnTimeout,并且用它内建的Builder初始化。下面我们仅仅给出FailOnTimeout内部的域以及一些核心方法。

public class FailOnTimeout extends Statement {
    private final Statement originalStatement;
    private final TimeUnit timeUnit;
    private final long timeout;
    private final boolean lookForStuckThread;

    private FailOnTimeout(Builder builder, Statement statement) {
        originalStatement = statement;
        timeout = builder.timeout;
        timeUnit = builder.unit;
        lookForStuckThread = builder.lookForStuckThread;
    }


    public static class Builder {
        private boolean lookForStuckThread = false;
        private long timeout = 0;
        private TimeUnit unit = TimeUnit.SECONDS;

        private Builder() {
        }

        public FailOnTimeout build(Statement statement) {
            if (statement == null) {
                throw new NullPointerException("statement cannot be null");
            }
            return new FailOnTimeout(this, statement);
        }
    }


    @Override
    public void evaluate() throws Throwable {
        CallableStatement callable = new CallableStatement();
        FutureTask task = new FutureTask(callable);
        ThreadGroup threadGroup = new ThreadGroup("FailOnTimeoutGroup");
        Thread thread = new Thread(threadGroup, task, "Time-limited test");
        thread.setDaemon(true);
        thread.start();
        callable.awaitStarted();
        Throwable throwable = getResult(task, thread);
        if (throwable != null) {
            throw throwable;
        }
    }

    private Throwable getResult(FutureTask task, Thread thread) {
        try {
            if (timeout > 0) {
                return task.get(timeout, timeUnit);
            } else {
                return task.get();
            }
        } catch (InterruptedException e) {
            return e; // caller will re-throw; no need to call Thread.interrupt()
        } catch (ExecutionException e) {
            // test failed; have caller re-throw the exception thrown by the test
            return e.getCause();
        } catch (TimeoutException e) {
            return createTimeoutException(thread);
        }
    }

    private class CallableStatement implements Callable {
        private final CountDownLatch startLatch = new CountDownLatch(1);

        public Throwable call() throws Exception {
            try {
                startLatch.countDown();
                originalStatement.evaluate();
            } catch (Exception e) {
                throw e;
            } catch (Throwable e) {
                return e;
            }
            return null;
        }

        public void awaitStarted() throws InterruptedException {
            startLatch.await();
        }
    }

}

可以看出它通过内置的Builder类来配置参数,通过CallableStatement和FutureTask启动新线程来运行真实的测试样例,并使用CountDownLatch来让父进程等待。实际的超时判断则借助了FutureTask的getResult,如果规定时间未返回结果就抛出超时异常。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65450.html

相关文章

  • Junit源码阅读(一)

    摘要:是对测试样例的建模,用来组合多个测试样例,是中的核心内容。也是一个虚类,子类应该实现方法来决定对于是否运行。如下列代码所示组合了和,为运行时异常和断言错误屏蔽了不一致的方面,可以向上提供错误信息和样例信息。 Junit的工程结构 showImg(/img/bVsEeS); 从上图可以清楚的看出Junit大致分为几个版块,接下来一一简略介绍这些版块的作用。 runner:定义了Jun...

    Gilbertat 评论0 收藏0
  • Junit源码阅读(六)之Junit中的设计模式

    摘要:前言在这次的博客中我们将着重于的许多集成性功能来讨论中的种种设计模式。装饰器模式装饰器模式是为了在原有功能上加入新功能,在中绝对属于使用最频繁架构中最核心的模式,等都是通过装饰器模式来完成扩展的。 前言 在这次的博客中我们将着重于Junit的许多集成性功能来讨论Junit中的种种设计模式。可以说Junit的实现本身就是GOF设计原则的范例教本,下面就让我们开始吧。 装饰器模式 装饰器...

    jlanglang 评论0 收藏0
  • jQuery源码分析系列之自调用匿名函数

    摘要:自调用匿名函数打开源码,首先你会看到这样的代码结构这是一个自调用匿名函数。这样子最大程度防止外界的变量定义对内部造成影响 自调用匿名函数 打开jQuery源码,首先你会看到这样的代码结构: (function(window,undefined){ //jquery code })(window); 这是一个自调用匿名函数。在第一个括号内,创建一个匿名函数;第二个括号内,立...

    psychola 评论0 收藏0
  • Junit源码阅读(三)之精致的Validator

    摘要:前言在建立的过程中,往往需要对当前的测试样例和注解进行验证,比如检查测试类是否含有非静态内部类,测试类是否是的。的验证机制非常精致而优美,在本次博客中我们就主要来谈一谈机制的实现。首先在中定义三个默认的类,如下。 前言 在建立Runner的过程中,往往需要对当前的测试样例和注解进行验证,比如检查测试类是否含有非静态内部类,测试类是否是Public的。Junit的验证机制非常精致而优美...

    李世赞 评论0 收藏0

发表评论

0条评论

Little_XM

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<