前言

上次的博客中我们着重介绍了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<TestRule> rules, Description description) {statement = applyAll(base, rules, description);}@Overridepublic void evaluate() throws Throwable {statement.evaluate();}private static Statement applyAll(Statement result, Iterable<TestRule> 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<TestRule> 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();}@Deprecatedpublic 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);}}@Overridepublic void evaluate() throws Throwable {CallableStatement callable = new CallableStatement();FutureTask<Throwable> task = new FutureTask<Throwable>(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<Throwable> 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 testreturn e.getCause();} catch (TimeoutException e) {return createTimeoutException(thread);}}private class CallableStatement implements Callable<Throwable> {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,如果规定时间未返回结果就抛出超时异常。

Junit源码阅读(四)之自定义扩展相关推荐

  1. mybatis源码阅读(四):mapper(dao)实例化

    转载自   mybatis源码阅读(四):mapper(dao)实例化 在开始分析之前,先来了解一下这个模块中的核心组件之间的关系,如图: 1.MapperRegistry&MapperPro ...

  2. 39 网络相关函数(七)——live555源码阅读(四)网络

    39 网络相关函数(七)--live555源码阅读(四)网络 39 网络相关函数(七)--live555源码阅读(四)网络 简介 14)readSocket从套接口读取数据 recv/recvfrom ...

  3. ThreadLocal源码阅读四:如何解决hash碰撞的?

    背景 推荐阅读ThreadLocal工作过程 推荐阅读ThreadLocal的魔数引发的疑问与思考 什么样的使用场景会出现hash碰撞? 如何解决hash碰撞的? 过程 可能产生hash碰撞的场景 分 ...

  4. Struts2源码阅读(四)_DispatcherConfigurationProvider续

    接下来第三步:init_LegacyStrutsProperties() 调用的是调用的是LegacyPropertiesConfigurationProvider 通过比较前面DefaultProp ...

  5. redis源码阅读-持久化之RDB

    持久化介绍: redis的持久化有两种方式: rdb :可以在指定的时间间隔内生成数据集的时间点快照(point-in-time snapshot) aof : 记录redis执行的所有写操作命令 根 ...

  6. redis源码阅读-zset

    前段时间给小伙伴分享redis,顺带又把redis撸了一遍了,对其源码,又有了比较深入的了解.(ps: 分享的文章再丰富下再放出来). 数据结构 我们先看下redis 5.0的代码.本次讲解主要是zs ...

  7. redis源码阅读-持久化之aof与aof重写详解

    aof相关配置 aof-rewrite-incremental-fsync yes # aof 开关,默认是关闭的,改为yes表示开启 appendonly no # aof的文件名,默认 appen ...

  8. mysql 1260,MYSQL 源码阅读 六

    前期节要 MYSQL源码阅读 一 MYSQL源码阅读 二 MYSQL源码阅读 三 MYSQL 源码阅读 四 MYSQL 源码阅读 五 上次有两个问题没搞明白 1 是 为什么一定要开启调试线程 ? 因为 ...

  9. Soul网关源码阅读(十)自定义简单插件编写

    Soul网关源码阅读(十)自定义简单插件编写 简介     综合前面所分析的插件处理流程相关知识,此次我们来编写自定义的插件:统计请求在插件链中的经历时长 编写准备     首先我们先探究一下,一个P ...

最新文章

  1. 用python画皮卡丘代码-用python绘制皮卡丘
  2. Nginx跨域配置、限制连接、限制下载速度
  3. 国内外主流BI工具介绍和点评
  4. 物联网核心安全系列——智能监控安全问题
  5. 线程安全的CopyOnWriteArrayList介绍
  6. Sass基础——Rem与Px的转换
  7. mysql的空白值mac,Mac下mysql安装启动遇到的坑,及数据库常用指令
  8. 计算机软件工程师报名条件,中级软件工程师证书报考条件有哪些要求?
  9. 服务器带宽10M能带多少人同时访问之并发数计算
  10. java table数据转excel,excel将数据转化成表格-如何将java数据转换成Excel表格
  11. 想做好物流成本控制,得秉承这个思路
  12. web前端项目(一) 做一个网易考拉官网 常规静态页面 + 页面放到http服务 + 前后端分离
  13. 享元模式(Flyweight模式)
  14. php网页右下方广告窗口,javascript实现右下角广告框效果
  15. FFMpeg使用vs出现声明被否决解决方案
  16. 【翻译】Sencha Ext JS 5发布
  17. TI CC1101学习笔记:工作原理简单入门
  18. 全球及中国熏蒸杀虫剂行业动态调查与消费规模预测报告2022年
  19. java常用类:1。包装类(以Integer类为例)2.String类 3.StringBuffer
  20. html项目答辩开场白,课题结题答辩开场白和结束语怎么写

热门文章

  1. HTML5 多图片上传(前端+后台详解)
  2. python相比于excel的优势_对照Excel使用Python进行数据分析,更快掌握
  3. trackingmore快递查询平台_国际快递物流信息追踪查询
  4. redhat linux 安装ftp服务,RedHat Linux 9.0为例介绍一下如何安装和配置vsftpd服务器
  5. multisim秒信号发生器_失联50年的卫星突然出现,莫名向地球发送信号,究竟谁在操控?...
  6. php 数组元素往后移动,php 二维数组 元素移动
  7. mysql in 保持顺序_IN条件结果顺序问题_MySQL
  8. icmp端口_pingtunnel搭建icmp隧道
  9. 判断回文数和求斐波拉序列
  10. MySQL NULL 值处理