资讯专栏INFORMATION COLUMN

Junit源码阅读(二)之样例运行的机制

meteor199 / 2876人阅读

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

前言

在上次的博客中我们提到了最终由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, 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将对应member加入该annotation映射的member列表。下面给一个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包装了方法信息以及方法相关的注解以及一些基本的验证方法比如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 afters;

   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执行时会先执行旧有的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> 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;
    }
}
在注解中加上参数 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 createRunnersForParameters(
                Iterable allParameters, String namePattern,
                ParametersRunnerFactory runnerFactory) throws Exception {
            try {
                List tests = createTestsForParameters(
                        allParameters, namePattern);
                List runners = new ArrayList();
                for (TestWithParameters test : tests) {
                    runners.add(runnerFactory
                            .createRunnerForTestWithParameters(test));
                }
                return runners;
            } catch (ClassCastException e) {
                throw parametersMethodReturnedWrongType();
            }
        }

        private List createRunners() throws Throwable {
            Parameters parameters = getParametersMethod().getAnnotation(
                    Parameters.class);
            return Collections.unmodifiableList(createRunnersForParameters(
                    allParameters(), parameters.name(),
                    getParametersRunnerFactory()));
        }           
               
                                           
                       
                 
            
                     
             
               

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/65452.html

相关文章

  • Junit源码阅读(一)

    摘要:是对测试样例的建模,用来组合多个测试样例,是中的核心内容。也是一个虚类,子类应该实现方法来决定对于是否运行。如下列代码所示组合了和,为运行时异常和断言错误屏蔽了不一致的方面,可以向上提供错误信息和样例信息。 Junit的工程结构 showImg(/img/bVsEeS); 从上图可以清楚的看出Junit大致分为几个版块,接下来一一简略介绍这些版块的作用。 runner:定义了Jun...

    Gilbertat 评论0 收藏0
  • Junit源码阅读(五)

    摘要:的作用是包装从生成的逻辑,提供两种方案生成和。最后从生成也异常简单,也就是实现其方法返回该。 前言 尽管在第二次博客中我们讲述了Runner的运行机制,但是许多其他特性比如Filter是如何与运行流程结合却并不清楚。这次我们来回顾整理一下Junit的执行流程,给出各种特性生效的机理,并分析一些代码中精妙的地方。 Junit的执行流程 JUnitCore的RunMain方法,使用jUn...

    vpants 评论0 收藏0
  • Junit源码阅读(六)之Junit设计模式

    摘要:前言在这次的博客中我们将着重于的许多集成性功能来讨论中的种种设计模式。装饰器模式装饰器模式是为了在原有功能上加入新功能,在中绝对属于使用最频繁架构中最核心的模式,等都是通过装饰器模式来完成扩展的。 前言 在这次的博客中我们将着重于Junit的许多集成性功能来讨论Junit中的种种设计模式。可以说Junit的实现本身就是GOF设计原则的范例教本,下面就让我们开始吧。 装饰器模式 装饰器...

    jlanglang 评论0 收藏0
  • Junit源码阅读(四)之自定义扩展

    摘要:前言上次的博客中我们着重介绍了的机制,这次我们将聚焦到自定义扩展上来。在很多情形下我们需要在测试过程中加入一些自定义的动作,这些就需要对进行包装,为此提供了以接口和为基础的扩展机制。 前言 上次的博客中我们着重介绍了Junit的Validator机制,这次我们将聚焦到自定义扩展Rule上来。在很多情形下我们需要在测试过程中加入一些自定义的动作,这些就需要对statement进行包装,...

    Little_XM 评论0 收藏0
  • Junit源码阅读(三)之精致Validator

    摘要:前言在建立的过程中,往往需要对当前的测试样例和注解进行验证,比如检查测试类是否含有非静态内部类,测试类是否是的。的验证机制非常精致而优美,在本次博客中我们就主要来谈一谈机制的实现。首先在中定义三个默认的类,如下。 前言 在建立Runner的过程中,往往需要对当前的测试样例和注解进行验证,比如检查测试类是否含有非静态内部类,测试类是否是Public的。Junit的验证机制非常精致而优美...

    李世赞 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<