Junit源码阅读(四)之自定义扩展
前言
上次的博客中我們著重介紹了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源码阅读(四)之自定义扩展的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Activity的4种启
- 下一篇: C语言 基础60题(2)——二维数组操作