前言

在上次的博客中我们提到了最终由Runner以Notifier为参数执行测试样例,但并没有解释到底测试方法是如何被运行起来的,一些诸如RunWith、RunAfter之类的特性又到底是如何实现的呢。这次我们就集中深入Runner的运行机制来探究样例是如何被运行的。

包装注解信息——FrameWorkMember

首先我们需要把注解等用户配置信息收集起来并attach到对应的方法、类和属性上,为了在之后的代码中能够方便的取到这些信息,我们要包装原有的类、方法和域,分别如下。

TestClass

TestClass包含原有的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<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的方法列表截图,大家可以感受一下。

TestClass部分方法列表

FrameWorkMethod

我们先给出它的父类FrameWorkMember的定义

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覆盖的判断方法,除父类要求外它主要提供的信息如下:

  • Annotations

  • Method

  • ReturnType

  • ParameterTypes

FrameWorkField

同FrameWorkMethod差不多,FrameWorkField和它继承自同一父类,较为简单,此处就不再详细介绍了。

真正的执行单元——Statement

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为参数运行。

组合方法测试的Runner实现——BlockJunitClassRunner

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);
    }
}

组合类测试的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<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;
    }
}

在注解中加上参数

BlockJUnit4ClassRunnerWithParameters

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 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()));
        }

suemi
1.5k 声望102 粉丝

程序猿一只,研究僧一枚