Junit源碼閱讀(二)之樣例運行的機制


前言

在上次的博客中咱們提到了最終由Runner以Notifier爲參數執行測試樣例,但並無解釋到底測試方法是如何被運行起來的,一些諸如RunWith、RunAfter之類的特性又究竟是如何實現的呢。此次咱們就集中深刻Runner的運行機制來探究樣例是如何被運行的。java

包裝註解信息——FrameWorkMember

首先咱們須要把註解等用戶配置信息收集起來並attach到對應的方法、類和屬性上,爲了在以後的代碼中可以方便的取到這些信息,咱們要包裝原有的類、方法和域,分別以下。ide

TestClass

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

TestClass部分方法列表

FrameWorkMethod

咱們先給出它的父類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

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()));
        }
相關文章
相關標籤/搜索