在上次的博客中咱們提到了最終由Runner以Notifier爲參數執行測試樣例,但並無解釋到底測試方法是如何被運行起來的,一些諸如RunWith、RunAfter之類的特性又究竟是如何實現的呢。此次咱們就集中深刻Runner的運行機制來探究樣例是如何被運行的。java
首先咱們須要把註解等用戶配置信息收集起來並attach到對應的方法、類和屬性上,爲了在以後的代碼中可以方便的取到這些信息,咱們要包裝原有的類、方法和域,分別以下。ide
TestClass包含原有的clazz信息,而且維護了兩個Map來管理它所包含的方法與屬性,每一個map的鍵是註解,而值是標上註解的FrameWorkMethod或FrameWorkField。同時TestClass還默認內置兩個Comparator來排序本身所包含的方法和屬性。測試
下面給出如何構造一個TestClass的代碼。ui
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<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations = new LinkedHashMap<Class<? extends Annotation>, List<FrameworkMethod>>(); Map<Class<? extends Annotation>, List<FrameworkField>> fieldsForAnnotations = new LinkedHashMap<Class<? extends Annotation>, List<FrameworkField>>(); scanAnnotatedMembers(methodsForAnnotations, fieldsForAnnotations); this.methodsForAnnotations = makeDeeplyUnmodifiable(methodsForAnnotations); this.fieldsForAnnotations = makeDeeplyUnmodifiable(fieldsForAnnotations); } protected void scanAnnotatedMembers(Map<Class<? extends Annotation>, List<FrameworkMethod>> methodsForAnnotations, Map<Class<? extends Annotation>, List<FrameworkField>> 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的方法列表截圖,你們能夠感覺一下。this
咱們先給出它的父類FrameWorkMember的定義lua
public abstract class FrameworkMember<T extends FrameworkMember<T>> implements Annotatable { abstract boolean isShadowedBy(T otherMember); boolean isShadowedBy(List<T> 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覆蓋的判斷方法,除父類要求外它主要提供的信息以下:spa
Annotations3d
Methodcode
ReturnTypeorm
ParameterTypes
同FrameWorkMethod差很少,FrameWorkField和它繼承自同一父類,較爲簡單,此處就再也不詳細介紹了。
Statement是最小的執行單元,諸如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 List<FrameworkMethod> afters; public RunAfters(Statement next, List<FrameworkMethod> afters, Object target) { this.next = next; this.afters = afters; this.target = target; } @Override public void evaluate() throws Throwable { List<Throwable> errors = new ArrayList<Throwable>(); 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爲參數運行。
Junit使用虛類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); } }
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<Class<?>> parents = new HashSet<Class<?>>(); /** * 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<Runner> runners(Class<?> parent, Class<?>[] children) throws InitializationError { addParent(parent); try { return runners(children); } finally { removeParent(parent); } } public List<Runner> runners(Class<?> parent, List<Class<?>> children) throws InitializationError { return runners(parent, children.toArray(new Class<?>[0])); } private List<Runner> runners(Class<?>[] children) { List<Runner> runners = new ArrayList<Runner>(); 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繼承了Suite,用來完成對多個類的組合測試的帶參數版本。它提供三大註解——Parameters、Parameter、UseParametersRunnerFactory,前二者是用來指定參數的,後者用來指定對於每個測試類如何生成Runner的工廠,默認工廠返回BlockJUnit4ClassRunnerWithParameters。咱們下面給出內置的工廠類如何建立runner的代碼。
private List<Runner> createRunnersForParameters( Iterable<Object> allParameters, String namePattern, ParametersRunnerFactory runnerFactory) throws Exception { try { List<TestWithParameters> tests = createTestsForParameters( allParameters, namePattern); List<Runner> runners = new ArrayList<Runner>(); for (TestWithParameters test : tests) { runners.add(runnerFactory .createRunnerForTestWithParameters(test)); } return runners; } catch (ClassCastException e) { throw parametersMethodReturnedWrongType(); } } private List<Runner> createRunners() throws Throwable { Parameters parameters = getParametersMethod().getAnnotation( Parameters.class); return Collections.unmodifiableList(createRunnersForParameters( allParameters(), parameters.name(), getParametersRunnerFactory())); }