在上次的博客中咱们提到了最终由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())); }