摘要:前言上次的博客中我們著重介紹了的機制,這次我們將聚焦到自定義擴展上來。在很多情形下我們需要在測試過程中加入一些自定義的動作,這些就需要對進行包裝,為此提供了以接口和為基礎的擴展機制。
前言
上次的博客中我們著重介紹了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) { ListTimeOut示例classRules = classRules(); return classRules.isEmpty() ? statement : new RunRules(statement, classRules, getDescription()); }
接下來我們以超時擴展為示例來看一看一個擴展是如何起作用的。
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(); FutureTasktask = 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,如果規定時間未返回結果就拋出超時異常。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65450.html
摘要:是對測試樣例的建模,用來組合多個測試樣例,是中的核心內容。也是一個虛類,子類應該實現方法來決定對于是否運行。如下列代碼所示組合了和,為運行時異常和斷言錯誤屏蔽了不一致的方面,可以向上提供錯誤信息和樣例信息。 Junit的工程結構 showImg(/img/bVsEeS); 從上圖可以清楚的看出Junit大致分為幾個版塊,接下來一一簡略介紹這些版塊的作用。 runner:定義了Jun...
摘要:前言在這次的博客中我們將著重于的許多集成性功能來討論中的種種設計模式。裝飾器模式裝飾器模式是為了在原有功能上加入新功能,在中絕對屬于使用最頻繁架構中最核心的模式,等都是通過裝飾器模式來完成擴展的。 前言 在這次的博客中我們將著重于Junit的許多集成性功能來討論Junit中的種種設計模式。可以說Junit的實現本身就是GOF設計原則的范例教本,下面就讓我們開始吧。 裝飾器模式 裝飾器...
摘要:自調用匿名函數打開源碼,首先你會看到這樣的代碼結構這是一個自調用匿名函數。這樣子最大程度防止外界的變量定義對內部造成影響 自調用匿名函數 打開jQuery源碼,首先你會看到這樣的代碼結構: (function(window,undefined){ //jquery code })(window); 這是一個自調用匿名函數。在第一個括號內,創建一個匿名函數;第二個括號內,立...
摘要:前言在建立的過程中,往往需要對當前的測試樣例和注解進行驗證,比如檢查測試類是否含有非靜態內部類,測試類是否是的。的驗證機制非常精致而優美,在本次博客中我們就主要來談一談機制的實現。首先在中定義三個默認的類,如下。 前言 在建立Runner的過程中,往往需要對當前的測試樣例和注解進行驗證,比如檢查測試類是否含有非靜態內部類,測試類是否是Public的。Junit的驗證機制非常精致而優美...
閱讀 1628·2021-10-12 10:11
閱讀 3748·2021-09-03 10:35
閱讀 1439·2019-08-30 15:55
閱讀 2122·2019-08-30 15:54
閱讀 993·2019-08-30 13:07
閱讀 1004·2019-08-30 11:09
閱讀 569·2019-08-29 13:21
閱讀 2645·2019-08-29 11:32