摘要:前言在上次的博客中我們提到了最終由以為參數執行測試樣例,但并沒有解釋到底測試方法是如何被運行起來的,一些諸如之類的特性又到底是如何實現的呢。這次我們就集中深入的運行機制來探究樣例是如何被運行的。使用拿到的直接運行方法。
前言
在上次的博客中我們提到了最終由Runner以Notifier為參數執行測試樣例,但并沒有解釋到底測試方法是如何被運行起來的,一些諸如RunWith、RunAfter之類的特性又到底是如何實現的呢。這次我們就集中深入Runner的運行機制來探究樣例是如何被運行的。
包裝注解信息——FrameWorkMember首先我們需要把注解等用戶配置信息收集起來并attach到對應的方法、類和屬性上,為了在之后的代碼中能夠方便的取到這些信息,我們要包裝原有的類、方法和域,分別如下。
TestClassTestClass包含原有的clazz信息,并且維護了兩個Map來管理它所包含的方法與屬性,每個map的鍵是注解,而值是標上注解的FrameWorkMethod或FrameWorkField。同時TestClass還默認內置兩個Comparator來排序自己所包含的方法和屬性。
下面給出如何構造一個TestClass的代碼。
public TestClass(Class> clazz) { this.clazz = clazz; if (clazz != null && clazz.getConstructors().length > 1) { throw new IllegalArgumentException( "Test class can only have one constructor"); } Map, List > methodsForAnnotations = new LinkedHashMap , List >(); Map , List > fieldsForAnnotations = new LinkedHashMap , List >(); scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations); this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations); this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations); } protected void scanAnnotatedMembers(Map , List > methodsForAnnotations, Map , List > fieldsForAnnotations) { for (Class> eachClass : getSuperClasses(clazz)) { for (Method eachMethod : MethodSorter.getDeclaredMethods(eachClass)) { addToAnnotationLists(new FrameworkMethod(eachMethod), methodsForAnnotations); } // ensuring fields are sorted to make sure that entries are inserted // and read from fieldForAnnotations in a deterministic order for (Field eachField : getSortedDeclaredFields(eachClass)) { addToAnnotationLists(new FrameworkField(eachField), fieldsForAnnotations); } } }
TestClass的主要功能就是向Runner提供clazz信息以及附帶的注解信息,上文的addToAnnotationLists將對應member加入該annotation映射的member列表。下面給一個TestClass的方法列表截圖,大家可以感受一下。
FrameWorkMethod我們先給出它的父類FrameWorkMember的定義
public abstract class FrameworkMember> implements Annotatable { abstract boolean isShadowedBy(T otherMember); boolean isShadowedBy(List members) { for (T each : members) { if (isShadowedBy(each)) { return true; } } return false; } protected abstract int getModifiers(); /** * Returns true if this member is static, false if not. */ public boolean isStatic() { return Modifier.isStatic(getModifiers()); } /** * Returns true if this member is public, false if not. */ public boolean isPublic() { return Modifier.isPublic(getModifiers()); } public abstract String getName(); public abstract Class> getType(); public abstract Class> getDeclaringClass(); }
FrameWorkMethod包裝了方法信息以及方法相關的注解以及一些基本的驗證方法比如validatePublicVoid和是否被其他FrameWorkMethod覆蓋的判斷方法,除父類要求外它主要提供的信息如下:
Annotations
Method
ReturnType
ParameterTypes
FrameWorkField同FrameWorkMethod差不多,FrameWorkField和它繼承自同一父類,較為簡單,此處就不再詳細介紹了。
真正的執行單元——StatementStatement是最小的執行單元,諸如RunAfter、RunWith等功能均是通過嵌套Statement來實現的,下面我們先給出Statement的定義,再給出一個嵌套的例子。
public abstract class Statement { /** * Run the action, throwing a {@code Throwable} if anything goes wrong. */ public abstract void evaluate() throws Throwable; }
下面以RunAfter的實現為例來說明:
public class RunAfters extends Statement { private final Statement next; private final Object target; private final Listafters; public RunAfters(Statement next, List afters, Object target) { this.next = next; this.afters = afters; this.target = target; } @Override public void evaluate() throws Throwable { List errors = new ArrayList (); try { next.evaluate(); } catch (Throwable e) { errors.add(e); } finally { for (FrameworkMethod each : afters) { try { each.invokeExplosively(target); } catch (Throwable e) { errors.add(e); } } } MultipleFailureException.assertEmpty(errors); } }
可以看出新的Statement執行時會先執行舊有的Statement,再將附加上的一系列方法以target為參數運行。
組合方法測試的Runner實現——BlockJunitClassRunnerJunit使用虛類ParentRunner來管理復合的Runner,使用composite模式,而BlockJunitClassRunner是ParentRunner的一個子類,主要負責同一測試類多個方法的組合測試,也就是最常用的情形。我們首先還是聚焦在如何運行測試樣例上。
首先看ParentRunner如何實現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); } }
這里有個classBlock方法用來提供真正運行的Statement,下面我們看一看
protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); if (!areAllChildrenIgnored()) { statement = withBeforeClasses(statement); statement = withAfterClasses(statement); statement = withClassRules(statement); } return statement; }
這個過程就是先通過反射獲得初始Statement,然后附加上RunBefore、RunAfter、用戶自定義Rule,我們來看一下初始Statement是如何生成的。
其過程是先取得所有通過過濾器的Childeren,再使用內置的調度器來分別按順序調用runChild方法,下面我們給出BlockJunit4ClassRunner的runChild方法
@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); } }
這里面最重要的就是RunLeaf也就是原子測試方法以及如何為單個方法生成的Statement——methodBlock,我們在下面分別給出。
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(); } }
RunLeaf的邏輯并不難,先通知Notifier測試開始,再直接調用statement的evaluate方法,最后通知Notifier測試結束。我們再來看看statement是如何生成的。
protected Statement methodBlock(final FrameworkMethod method) { Object test; try { test = new ReflectiveCallable() { @Override protected Object runReflectiveCall() throws Throwable { return createTest(method); } }.run(); } catch (Throwable e) { return new Fail(e); } Statement statement = methodInvoker(method, test); statement = possiblyExpectingExceptions(method, test, statement); statement = withPotentialTimeout(method, test, statement); statement = withBefores(method, test, statement); statement = withAfters(method, test, statement); statement = withRules(method, test, statement); return statement; }
上述代碼的邏輯還是比較復雜的,這里簡單概述一下,首先構造測試類的實例,然后為對應method構造statement的子類InvokeMethod,然后調用FrameWorkMethod的反射運行方法,如下:
public class InvokeMethod extends Statement { private final FrameworkMethod testMethod; private final Object target; public InvokeMethod(FrameworkMethod testMethod, Object target) { this.testMethod = testMethod; this.target = target; } @Override public void evaluate() throws Throwable { testMethod.invokeExplosively(target); } }組合類測試的Runner實現——Suite
Suite是對于ParentRunner的另一子類實現,主要用于多個測試類的情形。Suite自己維護一個runner列表,實現了getChilderen方法,其層次是在上文中提到的runChildren里,這一部分需要取出children節點然后調用runChild方法。我們著重考察suite和BlockJunit4ClassRunner在getChildren和runChild方法上的區別。Suite通過用戶傳入的runnerBuilder為每個類多帶帶建立runner作為children返回,而后者則返回帶Test注解的FrameWorkMethod列表。使用getChildren拿到的runner直接運行run方法。下面我們給出RunnerBuilder是如何為一系列測試類提供一系列對應的Runner,說來也簡單,就是使用為單個類建立Runner的方法為每個測試類建立最后組成一個集合。但是此處需要防止遞歸——this builder will throw an exception if it is requested for another runner for {@code parent} before this call completes(說實話這段如何防止遞歸我也沒看懂,有看懂的兄弟求教)。對于Suite而言,一般就是它維護一個BlockJUnit4ClassRunner列表。
public abstract class RunnerBuilder { private final Set在注解中加上參數 BlockJUnit4ClassRunnerWithParameters> parents = new HashSet >(); /** * Override to calculate the correct runner for a test class at runtime. * * @param testClass class to be run * @return a Runner * @throws Throwable if a runner cannot be constructed */ public abstract Runner runnerForClass(Class> testClass) throws Throwable; /** * Always returns a runner, even if it is just one that prints an error instead of running tests. * * @param testClass class to be run * @return a Runner */ public Runner safeRunnerForClass(Class> testClass) { try { return runnerForClass(testClass); } catch (Throwable e) { return new ErrorReportingRunner(testClass, e); } } Class> addParent(Class> parent) throws InitializationError { if (!parents.add(parent)) { throw new InitializationError(String.format("class "%s" (possibly indirectly) contains itself as a SuiteClass", parent.getName())); } return parent; } void removeParent(Class> klass) { parents.remove(klass); } /** * Constructs and returns a list of Runners, one for each child class in * {@code children}. Care is taken to avoid infinite recursion: * this builder will throw an exception if it is requested for another * runner for {@code parent} before this call completes. */ public List runners(Class> parent, Class>[] children) throws InitializationError { addParent(parent); try { return runners(children); } finally { removeParent(parent); } } public List runners(Class> parent, List > children) throws InitializationError { return runners(parent, children.toArray(new Class>[0])); } private List runners(Class>[] children) { List runners = new ArrayList (); for (Class> each : children) { Runner childRunner = safeRunnerForClass(each); if (childRunner != null) { runners.add(childRunner); } } return runners; } }
Junit使用BlockJUnit4ClassRunnerWithParameters繼承BlockJUnit4ClassRunner來完成對于組合方法的帶參數測試。它覆寫了createTest方法和對構造器和域的驗證方法。
@Override public Object createTest() throws Exception { InjectionType injectionType = getInjectionType(); switch (injectionType) { case CONSTRUCTOR: return createTestUsingConstructorInjection(); case FIELD: return createTestUsingFieldInjection(); default: throw new IllegalStateException("The injection type " + injectionType + " is not supported."); } }Parameterized
Parameterized繼承了Suite,用來完成對多個類的組合測試的帶參數版本。它提供三大注解——Parameters、Parameter、UseParametersRunnerFactory,前兩者是用來指定參數的,后者用來指定對于每一個測試類如何生成Runner的工廠,默認工廠返回BlockJUnit4ClassRunnerWithParameters。我們下面給出內置的工廠類如何創建runner的代碼。
private ListcreateRunnersForParameters( Iterable
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/65452.html
摘要:是對測試樣例的建模,用來組合多個測試樣例,是中的核心內容。也是一個虛類,子類應該實現方法來決定對于是否運行。如下列代碼所示組合了和,為運行時異常和斷言錯誤屏蔽了不一致的方面,可以向上提供錯誤信息和樣例信息。 Junit的工程結構 showImg(/img/bVsEeS); 從上圖可以清楚的看出Junit大致分為幾個版塊,接下來一一簡略介紹這些版塊的作用。 runner:定義了Jun...
摘要:的作用是包裝從生成的邏輯,提供兩種方案生成和。最后從生成也異常簡單,也就是實現其方法返回該。 前言 盡管在第二次博客中我們講述了Runner的運行機制,但是許多其他特性比如Filter是如何與運行流程結合卻并不清楚。這次我們來回顧整理一下Junit的執行流程,給出各種特性生效的機理,并分析一些代碼中精妙的地方。 Junit的執行流程 JUnitCore的RunMain方法,使用jUn...
摘要:前言在這次的博客中我們將著重于的許多集成性功能來討論中的種種設計模式。裝飾器模式裝飾器模式是為了在原有功能上加入新功能,在中絕對屬于使用最頻繁架構中最核心的模式,等都是通過裝飾器模式來完成擴展的。 前言 在這次的博客中我們將著重于Junit的許多集成性功能來討論Junit中的種種設計模式。可以說Junit的實現本身就是GOF設計原則的范例教本,下面就讓我們開始吧。 裝飾器模式 裝飾器...
摘要:前言上次的博客中我們著重介紹了的機制,這次我們將聚焦到自定義擴展上來。在很多情形下我們需要在測試過程中加入一些自定義的動作,這些就需要對進行包裝,為此提供了以接口和為基礎的擴展機制。 前言 上次的博客中我們著重介紹了Junit的Validator機制,這次我們將聚焦到自定義擴展Rule上來。在很多情形下我們需要在測試過程中加入一些自定義的動作,這些就需要對statement進行包裝,...
摘要:前言在建立的過程中,往往需要對當前的測試樣例和注解進行驗證,比如檢查測試類是否含有非靜態內部類,測試類是否是的。的驗證機制非常精致而優美,在本次博客中我們就主要來談一談機制的實現。首先在中定義三個默認的類,如下。 前言 在建立Runner的過程中,往往需要對當前的測試樣例和注解進行驗證,比如檢查測試類是否含有非靜態內部類,測試類是否是Public的。Junit的驗證機制非常精致而優美...
閱讀 2687·2023-04-25 15:15
閱讀 1327·2021-11-25 09:43
閱讀 1615·2021-11-23 09:51
閱讀 1091·2021-11-12 10:36
閱讀 2893·2021-11-11 16:55
閱讀 967·2021-11-08 13:18
閱讀 738·2021-10-28 09:31
閱讀 2063·2019-08-30 15:47