摘要:的作用是包裝從生成的邏輯,提供兩種方案生成和。最后從生成也異常簡單,也就是實現其方法返回該。
前言
盡管在第二次博客中我們講述了Runner的運行機制,但是許多其他特性比如Filter是如何與運行流程結合卻并不清楚。這次我們來回顧整理一下Junit的執行流程,給出各種特性生效的機理,并分析一些代碼中精妙的地方。
Junit的執行流程JUnitCore的RunMain方法,使用jUnitCommandLineParseResult解析參數并生成Request。
Result runMain(JUnitSystem system, String... args) { system.out().println("JUnit version " + Version.id()); JUnitCommandLineParseResult jUnitCommandLineParseResult = JUnitCommandLineParseResult.parse(args); RunListener listener = new TextListener(system); addListener(listener); return run(jUnitCommandLineParseResult.createRequest(defaultComputer())); }
在jUnitCommandLineParseResult的createRequest方法中,調用Request的classes方法生成Request,并對生成的Request進行過濾
public Request createRequest(Computer computer) { if (parserErrors.isEmpty()) { Request request = Request.classes( computer, classes.toArray(new Class>[classes.size()])); return applyFilterSpecs(request); } else { return errorReport(new InitializationError(parserErrors)); } }
接下來我們就要進入核心部分了,先提出以下幾個問題:
如何為單個類生成Request
Filter的實現機制
對于錯誤如何把它納入以Request為初始并最終使用Runner的run這一套機制中
我們先回答第一個問題,其他問題我們會在之后慢慢解答:
接下來先給出classes方法的代碼
public static Request classes(Computer computer, Class>... classes) { try { AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true); Runner suite = computer.getSuite(builder, classes); return runner(suite); } catch (InitializationError e) { return runner(new ErrorReportingRunner(e, classes)); } }
問題再次被拆分為三個:
生成一個Builder
從Builder導出一個Runner
從Runner生成一個Request
AllDefaultPossibilitiesBuilder的邏輯是先后使用ignoredBuilder、annotatedBuilder、suiteMethodBuilder、junit3Builder、junit4Builder來生成Runner直到有一個成功則返回,然后getSuite方法將返回的Runner變為Suite。
Computer的作用是包裝從builder生成Runner的邏輯,提供兩種方案——生成SingleClassRunner和Suite。我們在這里提一下生成Suite的邏輯,就是使用該builder不停為多個測試類生成對應的Runner并放置到Suite的Runner列表中,Suite其他的初始化過程依從其父類,此處就不詳述。
最后從Runner生成Request也異常簡單,也就是實現其getRunner方法返回該Runner。現在我們把注意力投放到builder如何導出Runner,以JUnit4Builder為例,下面給出代碼:
public class JUnit4Builder extends RunnerBuilder { @Override public Runner runnerForClass(Class> testClass) throws Throwable { return new BlockJUnit4ClassRunner(testClass); } }
可以看出它其實就是直接生成了一個BlockJUnit4ClassRunner,下面我們關注該Runner的構造過程。
protected ParentRunner(Class> testClass) throws InitializationError { this.testClass = createTestClass(testClass); validate(); } protected TestClass createTestClass(Class> testClass) { return new TestClass(testClass); }
可以看出構造SingleClassRunne的過程就是解析生成TestClass的過程,我們在第二篇博客里已經詳細講解過了,此處就不再贅述了。可能許多讀者看到這兒會很困惑,之前一直強調的描述測試樣例的Description到底是在哪里生成的呢?其實是在Runner的run過程里生成的,如下:
@Override public void run(final RunNotifier notifier) { EachTestNotifier testNotifier = new EachTestNotifier(notifier, getDescription()); try { Statement statement = classBlock(notifier); statement.evaluate(); } catch (AssumptionViolatedException e) { testNotifier.addFailedAssumption(e); } catch (StoppedByUserException e) { throw e; } catch (Throwable e) { testNotifier.addFailure(e); } } @Override public Description getDescription() { Description description = Description.createSuiteDescription(getName(), getRunnerAnnotations()); for (T child : getFilteredChildren()) { description.addChild(describeChild(child)); } return description; }
可以明顯地得知Description是依據Runner來建立的,它的結構和Runner應該保持一致,它是Runner的附屬品,用來供與Runner交互的Notifier獲得信息。
Junit的Failure機制下面我們主要關注在運行之后Result的生成和Failure的處理。Result是Notifier通過fireTestRunFinished(result)生成的,我們來看一看它具體做了什么。
private abstract class SafeNotifier { private final ListcurrentListeners; SafeNotifier() { this(listeners); } SafeNotifier(List currentListeners) { this.currentListeners = currentListeners; } void run() { int capacity = currentListeners.size(); List safeListeners = new ArrayList (capacity); List failures = new ArrayList (capacity); for (RunListener listener : currentListeners) { try { notifyListener(listener); safeListeners.add(listener); } catch (Exception e) { failures.add(new Failure(Description.TEST_MECHANISM, e)); } } fireTestFailures(safeListeners, failures); } abstract protected void notifyListener(RunListener each) throws Exception; } public void fireTestRunFinished(final Result result) { new SafeNotifier() { @Override protected void notifyListener(RunListener each) throws Exception { each.testRunFinished(result); } }.run(); } public void fireTestAssumptionFailed(final Failure failure) { new SafeNotifier() { @Override protected void notifyListener(RunListener each) throws Exception { each.testAssumptionFailure(failure); } }.run(); } private void fireTestFailures(List listeners, final List failures) { if (!failures.isEmpty()) { new SafeNotifier(listeners) { @Override protected void notifyListener(RunListener listener) throws Exception { for (Failure each : failures) { listener.testFailure(each); } } }.run(); } }
簡單概括一下,就是調用它所管理的Listener的testRunFinished來處理Result,處理完畢之后就把該Listener標記為安全的,如果處理過程中出現異常,則將該異常加入failures列表,全部通知完畢后再次通知所有安全的Listener處理之前所有的Failure。這里還有一個問題,那就是Failure到底是怎樣在運行時生成的。由于斷言機制,所有的斷言失敗都會拋出對應的異常,對于斷言異常,請看下文:
@Override protected void runChild(final FrameworkMethod method, RunNotifier notifier) { Description description = describeChild(method); if (isIgnored(method)) { notifier.fireTestIgnored(description); } else { Statement statement; try { statement = methodBlock(method); } catch (Throwable ex) { statement = new Fail(ex); } runLeaf(statement, description, notifier); } }
Fail會直接拋出原有的異常,再次調用RunLeaf
protected final void runLeaf(Statement statement, Description description, RunNotifier notifier) { EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description); eachNotifier.fireTestStarted(); try { statement.evaluate(); } catch (AssumptionViolatedException e) { eachNotifier.addFailedAssumption(e); } catch (Throwable e) { eachNotifier.addFailure(e); } finally { eachNotifier.fireTestFinished(); } }
最后轉入addFailure處理,而addFailure會最終通知Notifier中的各個Listener處理
。那JunitCore到底使用了怎樣的Listener,它又執行了怎樣的操作。
public Result run(Runner runner) { Result result = new Result(); RunListener listener = result.createListener(); notifier.addFirstListener(listener); try { notifier.fireTestRunStarted(runner.getDescription()); runner.run(notifier); notifier.fireTestRunFinished(result); } finally { removeListener(listener); } return result; }
注意JunitCore的Run方法中加入了一個Result中內置的Listener,其定義如下
@RunListener.ThreadSafe private class Listener extends RunListener { @Override public void testRunStarted(Description description) throws Exception { startTime.set(System.currentTimeMillis()); } @Override public void testRunFinished(Result result) throws Exception { long endTime = System.currentTimeMillis(); runTime.addAndGet(endTime - startTime.get()); } @Override public void testFinished(Description description) throws Exception { count.getAndIncrement(); } @Override public void testFailure(Failure failure) throws Exception { failures.add(failure); } @Override public void testIgnored(Description description) throws Exception { ignoreCount.getAndIncrement(); } @Override public void testAssumptionFailure(Failure failure) { // do nothing: same as passing (for 4.5; may change in 4.6) } }
可以看出該Listener在testFailure中完成了添加Failure的動作,看到這里我簡直激動莫名,使用一個內置的Listener子類來避免顯式為Result添加Failure,而是依然把這些操作集中在Notifier——Listener的觀察者體系里,可謂精妙絕倫!
同時還需注意在runMain方法中加入了一個TextListener來完成打印結果的工作。
Junit的初始化錯誤處理上面我們講述了Junit如何處理樣例中的斷言錯誤以及運行時錯誤,但是當初始化Request的過程中一旦發生異常依然需要繼續運行并提示測試失敗,這又是怎么實現的呢?
我們回顧之前createRequest方法中的errorReport,代碼如下:
public static Request errorReport(Class> klass, Throwable cause) { return runner(new ErrorReportingRunner(klass, cause)); }
我們來看一下這個ErrorReportingRunner的實現
public class ErrorReportingRunner extends Runner { private final Listcauses; private final String classNames; public ErrorReportingRunner(Class> testClass, Throwable cause) { this(cause, new Class>[] { testClass }); } public ErrorReportingRunner(Throwable cause, Class>... testClasses) { if (testClasses == null || testClasses.length == 0) { throw new NullPointerException("Test classes cannot be null or empty"); } for (Class> testClass : testClasses) { if (testClass == null) { throw new NullPointerException("Test class cannot be null"); } } classNames = getClassNames(testClasses); causes = getCauses(cause); } @Override public Description getDescription() { Description description = Description.createSuiteDescription(classNames); for (Throwable each : causes) { description.addChild(describeCause(each)); } return description; } @Override public void run(RunNotifier notifier) { for (Throwable each : causes) { runCause(each, notifier); } } private String getClassNames(Class>... testClasses) { final StringBuilder builder = new StringBuilder(); for (Class> testClass : testClasses) { if (builder.length() != 0) { builder.append(", "); } builder.append(testClass.getName()); } return builder.toString(); } @SuppressWarnings("deprecation") private List getCauses(Throwable cause) { if (cause instanceof InvocationTargetException) { return getCauses(cause.getCause()); } if (cause instanceof InitializationError) { return ((InitializationError) cause).getCauses(); } if (cause instanceof org.junit.internal.runners.InitializationError) { return ((org.junit.internal.runners.InitializationError) cause) .getCauses(); } return Arrays.asList(cause); } private Description describeCause(Throwable child) { return Description.createTestDescription(classNames, "initializationError"); } private void runCause(Throwable child, RunNotifier notifier) { Description description = describeCause(child); notifier.fireTestStarted(description); notifier.fireTestFailure(new Failure(description, child)); notifier.fireTestFinished(description); } }
上面的大致邏輯依然是先生成Description(因為沒有到ParentRunner的run那一步,Description沒有生成,故需要在此生成),然后移交給Notifier處理
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/65449.html
摘要:是對測試樣例的建模,用來組合多個測試樣例,是中的核心內容。也是一個虛類,子類應該實現方法來決定對于是否運行。如下列代碼所示組合了和,為運行時異常和斷言錯誤屏蔽了不一致的方面,可以向上提供錯誤信息和樣例信息。 Junit的工程結構 showImg(/img/bVsEeS); 從上圖可以清楚的看出Junit大致分為幾個版塊,接下來一一簡略介紹這些版塊的作用。 runner:定義了Jun...
摘要:前言在這次的博客中我們將著重于的許多集成性功能來討論中的種種設計模式。裝飾器模式裝飾器模式是為了在原有功能上加入新功能,在中絕對屬于使用最頻繁架構中最核心的模式,等都是通過裝飾器模式來完成擴展的。 前言 在這次的博客中我們將著重于Junit的許多集成性功能來討論Junit中的種種設計模式。可以說Junit的實現本身就是GOF設計原則的范例教本,下面就讓我們開始吧。 裝飾器模式 裝飾器...
摘要:前言在建立的過程中,往往需要對當前的測試樣例和注解進行驗證,比如檢查測試類是否含有非靜態內部類,測試類是否是的。的驗證機制非常精致而優美,在本次博客中我們就主要來談一談機制的實現。首先在中定義三個默認的類,如下。 前言 在建立Runner的過程中,往往需要對當前的測試樣例和注解進行驗證,比如檢查測試類是否含有非靜態內部類,測試類是否是Public的。Junit的驗證機制非常精致而優美...
摘要:前言上次的博客中我們著重介紹了的機制,這次我們將聚焦到自定義擴展上來。在很多情形下我們需要在測試過程中加入一些自定義的動作,這些就需要對進行包裝,為此提供了以接口和為基礎的擴展機制。 前言 上次的博客中我們著重介紹了Junit的Validator機制,這次我們將聚焦到自定義擴展Rule上來。在很多情形下我們需要在測試過程中加入一些自定義的動作,這些就需要對statement進行包裝,...
摘要:添加依賴新建項目選擇三個依賴對于已存在的項目可以在加入,將會幫你自動配置好配置基本信息然后在下添加基本配置數據庫連接地址數據庫賬號數據庫密碼數據庫驅動創建實體創建一個實體,包含姓名年齡屬性創建數據訪問接口創建一個 添加依賴 新建項目選擇web,MyBatis,MySQL三個依賴 showImg(https://segmentfault.com/img/bV2l1L?w=1684&h=1...
閱讀 2985·2021-10-19 11:46
閱讀 979·2021-08-03 14:03
閱讀 2934·2021-06-11 18:08
閱讀 2905·2019-08-29 13:52
閱讀 2744·2019-08-29 12:49
閱讀 480·2019-08-26 13:56
閱讀 924·2019-08-26 13:41
閱讀 849·2019-08-26 13:35