摘要:前言在上次的博客中我們提到了最終由以為參數(shù)執(zhí)行測(cè)試樣例,但并沒(méi)有解釋到底測(cè)試方法是如何被運(yùn)行起來(lái)的,一些諸如之類的特性又到底是如何實(shí)現(xiàn)的呢。這次我們就集中深入的運(yùn)行機(jī)制來(lái)探究樣例是如何被運(yùn)行的。使用拿到的直接運(yùn)行方法。
前言
在上次的博客中我們提到了最終由Runner以Notifier為參數(shù)執(zhí)行測(cè)試樣例,但并沒(méi)有解釋到底測(cè)試方法是如何被運(yùn)行起來(lái)的,一些諸如RunWith、RunAfter之類的特性又到底是如何實(shí)現(xiàn)的呢。這次我們就集中深入Runner的運(yùn)行機(jī)制來(lái)探究樣例是如何被運(yùn)行的。
包裝注解信息——FrameWorkMember首先我們需要把注解等用戶配置信息收集起來(lái)并attach到對(duì)應(yīng)的方法、類和屬性上,為了在之后的代碼中能夠方便的取到這些信息,我們要包裝原有的類、方法和域,分別如下。
TestClassTestClass包含原有的clazz信息,并且維護(hù)了兩個(gè)Map來(lái)管理它所包含的方法與屬性,每個(gè)map的鍵是注解,而值是標(biāo)上注解的FrameWorkMethod或FrameWorkField。同時(shí)TestClass還默認(rèn)內(nèi)置兩個(gè)Comparator來(lái)排序自己所包含的方法和屬性。
下面給出如何構(gòu)造一個(gè)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將對(duì)應(yīng)member加入該annotation映射的member列表。下面給一個(gè)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包裝了方法信息以及方法相關(guān)的注解以及一些基本的驗(yàn)證方法比如validatePublicVoid和是否被其他FrameWorkMethod覆蓋的判斷方法,除父類要求外它主要提供的信息如下:
Annotations
Method
ReturnType
ParameterTypes
FrameWorkField同F(xiàn)rameWorkMethod差不多,F(xiàn)rameWorkField和它繼承自同一父類,較為簡(jiǎn)單,此處就不再詳細(xì)介紹了。
真正的執(zhí)行單元——StatementStatement是最小的執(zhí)行單元,諸如RunAfter、RunWith等功能均是通過(guò)嵌套Statement來(lái)實(shí)現(xiàn)的,下面我們先給出Statement的定義,再給出一個(gè)嵌套的例子。
public abstract class Statement { /** * Run the action, throwing a {@code Throwable} if anything goes wrong. */ public abstract void evaluate() throws Throwable; }
下面以RunAfter的實(shí)現(xiàn)為例來(lái)說(shuō)明:
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執(zhí)行時(shí)會(huì)先執(zhí)行舊有的Statement,再將附加上的一系列方法以target為參數(shù)運(yùn)行。
組合方法測(cè)試的Runner實(shí)現(xiàn)——BlockJunitClassRunnerJunit使用虛類ParentRunner來(lái)管理復(fù)合的Runner,使用composite模式,而B(niǎo)lockJunitClassRunner是ParentRunner的一個(gè)子類,主要負(fù)責(zé)同一測(cè)試類多個(gè)方法的組合測(cè)試,也就是最常用的情形。我們首先還是聚焦在如何運(yùn)行測(cè)試樣例上。
首先看ParentRunner如何實(shí)現(xiàn)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); } }
這里有個(gè)classBlock方法用來(lái)提供真正運(yùn)行的Statement,下面我們看一看
protected Statement classBlock(final RunNotifier notifier) { Statement statement = childrenInvoker(notifier); if (!areAllChildrenIgnored()) { statement = withBeforeClasses(statement); statement = withAfterClasses(statement); statement = withClassRules(statement); } return statement; }
這個(gè)過(guò)程就是先通過(guò)反射獲得初始Statement,然后附加上RunBefore、RunAfter、用戶自定義Rule,我們來(lái)看一下初始Statement是如何生成的。
其過(guò)程是先取得所有通過(guò)過(guò)濾器的Childeren,再使用內(nèi)置的調(diào)度器來(lái)分別按順序調(diào)用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也就是原子測(cè)試方法以及如何為單個(gè)方法生成的Statement——methodBlock,我們?cè)谙旅娣謩e給出。
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測(cè)試開(kāi)始,再直接調(diào)用statement的evaluate方法,最后通知Notifier測(cè)試結(jié)束。我們?cè)賮?lái)看看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; }
上述代碼的邏輯還是比較復(fù)雜的,這里簡(jiǎn)單概述一下,首先構(gòu)造測(cè)試類的實(shí)例,然后為對(duì)應(yīng)method構(gòu)造statement的子類InvokeMethod,然后調(diào)用FrameWorkMethod的反射運(yùn)行方法,如下:
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); } }組合類測(cè)試的Runner實(shí)現(xiàn)——Suite
Suite是對(duì)于ParentRunner的另一子類實(shí)現(xiàn),主要用于多個(gè)測(cè)試類的情形。Suite自己維護(hù)一個(gè)runner列表,實(shí)現(xiàn)了getChilderen方法,其層次是在上文中提到的runChildren里,這一部分需要取出children節(jié)點(diǎn)然后調(diào)用runChild方法。我們著重考察suite和BlockJunit4ClassRunner在getChildren和runChild方法上的區(qū)別。Suite通過(guò)用戶傳入的runnerBuilder為每個(gè)類多帶帶建立runner作為children返回,而后者則返回帶Test注解的FrameWorkMethod列表。使用getChildren拿到的runner直接運(yùn)行run方法。下面我們給出RunnerBuilder是如何為一系列測(cè)試類提供一系列對(duì)應(yīng)的Runner,說(shuō)來(lái)也簡(jiǎn)單,就是使用為單個(gè)類建立Runner的方法為每個(gè)測(cè)試類建立最后組成一個(gè)集合。但是此處需要防止遞歸——this builder will throw an exception if it is requested for another runner for {@code parent} before this call completes(說(shuō)實(shí)話這段如何防止遞歸我也沒(méi)看懂,有看懂的兄弟求教)。對(duì)于Suite而言,一般就是它維護(hù)一個(gè)BlockJUnit4ClassRunner列表。
public abstract class RunnerBuilder { private final Set在注解中加上參數(shù) 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來(lái)完成對(duì)于組合方法的帶參數(shù)測(cè)試。它覆寫(xiě)了createTest方法和對(duì)構(gòu)造器和域的驗(yàn)證方法。
@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,用來(lái)完成對(duì)多個(gè)類的組合測(cè)試的帶參數(shù)版本。它提供三大注解——Parameters、Parameter、UseParametersRunnerFactory,前兩者是用來(lái)指定參數(shù)的,后者用來(lái)指定對(duì)于每一個(gè)測(cè)試類如何生成Runner的工廠,默認(rèn)工廠返回BlockJUnit4ClassRunnerWithParameters。我們下面給出內(nèi)置的工廠類如何創(chuàng)建runner的代碼。
private ListcreateRunnersForParameters( Iterable
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/65452.html
摘要:是對(duì)測(cè)試樣例的建模,用來(lái)組合多個(gè)測(cè)試樣例,是中的核心內(nèi)容。也是一個(gè)虛類,子類應(yīng)該實(shí)現(xiàn)方法來(lái)決定對(duì)于是否運(yùn)行。如下列代碼所示組合了和,為運(yùn)行時(shí)異常和斷言錯(cuò)誤屏蔽了不一致的方面,可以向上提供錯(cuò)誤信息和樣例信息。 Junit的工程結(jié)構(gòu) showImg(/img/bVsEeS); 從上圖可以清楚的看出Junit大致分為幾個(gè)版塊,接下來(lái)一一簡(jiǎn)略介紹這些版塊的作用。 runner:定義了Jun...
摘要:的作用是包裝從生成的邏輯,提供兩種方案生成和。最后從生成也異常簡(jiǎn)單,也就是實(shí)現(xiàn)其方法返回該。 前言 盡管在第二次博客中我們講述了Runner的運(yùn)行機(jī)制,但是許多其他特性比如Filter是如何與運(yùn)行流程結(jié)合卻并不清楚。這次我們來(lái)回顧整理一下Junit的執(zhí)行流程,給出各種特性生效的機(jī)理,并分析一些代碼中精妙的地方。 Junit的執(zhí)行流程 JUnitCore的RunMain方法,使用jUn...
摘要:前言在這次的博客中我們將著重于的許多集成性功能來(lái)討論中的種種設(shè)計(jì)模式。裝飾器模式裝飾器模式是為了在原有功能上加入新功能,在中絕對(duì)屬于使用最頻繁架構(gòu)中最核心的模式,等都是通過(guò)裝飾器模式來(lái)完成擴(kuò)展的。 前言 在這次的博客中我們將著重于Junit的許多集成性功能來(lái)討論Junit中的種種設(shè)計(jì)模式。可以說(shuō)Junit的實(shí)現(xiàn)本身就是GOF設(shè)計(jì)原則的范例教本,下面就讓我們開(kāi)始吧。 裝飾器模式 裝飾器...
摘要:前言上次的博客中我們著重介紹了的機(jī)制,這次我們將聚焦到自定義擴(kuò)展上來(lái)。在很多情形下我們需要在測(cè)試過(guò)程中加入一些自定義的動(dòng)作,這些就需要對(duì)進(jìn)行包裝,為此提供了以接口和為基礎(chǔ)的擴(kuò)展機(jī)制。 前言 上次的博客中我們著重介紹了Junit的Validator機(jī)制,這次我們將聚焦到自定義擴(kuò)展Rule上來(lái)。在很多情形下我們需要在測(cè)試過(guò)程中加入一些自定義的動(dòng)作,這些就需要對(duì)statement進(jìn)行包裝,...
摘要:前言在建立的過(guò)程中,往往需要對(duì)當(dāng)前的測(cè)試樣例和注解進(jìn)行驗(yàn)證,比如檢查測(cè)試類是否含有非靜態(tài)內(nèi)部類,測(cè)試類是否是的。的驗(yàn)證機(jī)制非常精致而優(yōu)美,在本次博客中我們就主要來(lái)談一談機(jī)制的實(shí)現(xiàn)。首先在中定義三個(gè)默認(rèn)的類,如下。 前言 在建立Runner的過(guò)程中,往往需要對(duì)當(dāng)前的測(cè)試樣例和注解進(jìn)行驗(yàn)證,比如檢查測(cè)試類是否含有非靜態(tài)內(nèi)部類,測(cè)試類是否是Public的。Junit的驗(yàn)證機(jī)制非常精致而優(yōu)美...
閱讀 2675·2023-04-25 15:15
閱讀 1316·2021-11-25 09:43
閱讀 1604·2021-11-23 09:51
閱讀 1079·2021-11-12 10:36
閱讀 2880·2021-11-11 16:55
閱讀 955·2021-11-08 13:18
閱讀 723·2021-10-28 09:31
閱讀 2048·2019-08-30 15:47