资讯专栏INFORMATION COLUMN

Junit源码阅读(五)

vpants / 708人阅读

摘要:的作用是包装从生成的逻辑,提供两种方案生成和。最后从生成也异常简单,也就是实现其方法返回该。

前言

尽管在第二次博客中我们讲述了Runner的运行机制,但是许多其他特性比如Filter是如何与运行流程结合却并不清楚。这次我们来回顾整理一下Junit的执行流程,给出各种特性生效的机理,并分析一些代码中精妙的地方。

Junit的执行流程

JUnitCore的RunMain方法,使用jUnitCommandLineParseResult解析参数并生成Request。

Result runMain(JUnitSystem system, String... args) {
        system.out().println("JUnit version " + Version.id());

        JUnitCommandLineParseResult jUnitCommandLineParseResult = JUnitCommandLineParseResult.parse(args);

        RunListener listener = new TextListener(system);
        addListener(listener);

        return run(jUnitCommandLineParseResult.createRequest(defaultComputer()));
    }

在jUnitCommandLineParseResult的createRequest方法中,调用Request的classes方法生成Request,并对生成的Request进行过滤

public Request createRequest(Computer computer) {
        if (parserErrors.isEmpty()) {
            Request request = Request.classes(
                    computer, classes.toArray(new Class[classes.size()]));
            return applyFilterSpecs(request);
        } else {
            return errorReport(new InitializationError(parserErrors));
        }
    }

接下来我们就要进入核心部分了,先提出以下几个问题:

如何为单个类生成Request

Filter的实现机制

对于错误如何把它纳入以Request为初始并最终使用Runner的run这一套机制中

我们先回答第一个问题,其他问题我们会在之后慢慢解答:

接下来先给出classes方法的代码

public static Request classes(Computer computer, Class... classes) {
        try {
            AllDefaultPossibilitiesBuilder builder = new AllDefaultPossibilitiesBuilder(true);
            Runner suite = computer.getSuite(builder, classes);
            return runner(suite);
        } catch (InitializationError e) {
            return runner(new ErrorReportingRunner(e, classes));
        }
    }

问题再次被拆分为三个:

生成一个Builder

从Builder导出一个Runner

从Runner生成一个Request

AllDefaultPossibilitiesBuilder的逻辑是先后使用ignoredBuilder、annotatedBuilder、suiteMethodBuilder、junit3Builder、junit4Builder来生成Runner直到有一个成功则返回,然后getSuite方法将返回的Runner变为Suite。

Computer的作用是包装从builder生成Runner的逻辑,提供两种方案——生成SingleClassRunner和Suite。我们在这里提一下生成Suite的逻辑,就是使用该builder不停为多个测试类生成对应的Runner并放置到Suite的Runner列表中,Suite其他的初始化过程依从其父类,此处就不详述。

最后从Runner生成Request也异常简单,也就是实现其getRunner方法返回该Runner。现在我们把注意力投放到builder如何导出Runner,以JUnit4Builder为例,下面给出代码:

 public class JUnit4Builder extends RunnerBuilder {
    @Override
    public Runner runnerForClass(Class testClass) throws Throwable {
        return new BlockJUnit4ClassRunner(testClass);
    }
}

可以看出它其实就是直接生成了一个BlockJUnit4ClassRunner,下面我们关注该Runner的构造过程。

    protected ParentRunner(Class testClass) throws InitializationError {
        this.testClass = createTestClass(testClass);
        validate();
    }

    protected TestClass createTestClass(Class testClass) {
        return new TestClass(testClass);
    }

可以看出构造SingleClassRunne的过程就是解析生成TestClass的过程,我们在第二篇博客里已经详细讲解过了,此处就不再赘述了。可能许多读者看到这儿会很困惑,之前一直强调的描述测试样例的Description到底是在哪里生成的呢?其实是在Runner的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);
        }
    }
    @Override
    public Description getDescription() {
        Description description = Description.createSuiteDescription(getName(),
                getRunnerAnnotations());
        for (T child : getFilteredChildren()) {
            description.addChild(describeChild(child));
        }
        return description;
    }

可以明显地得知Description是依据Runner来建立的,它的结构和Runner应该保持一致,它是Runner的附属品,用来供与Runner交互的Notifier获得信息。

Junit的Failure机制

下面我们主要关注在运行之后Result的生成和Failure的处理。Result是Notifier通过fireTestRunFinished(result)生成的,我们来看一看它具体做了什么。

    private abstract class SafeNotifier {
        private final List currentListeners;

        SafeNotifier() {
            this(listeners);
        }

        SafeNotifier(List currentListeners) {
            this.currentListeners = currentListeners;
        }

        void run() {
            int capacity = currentListeners.size();
            List safeListeners = new ArrayList(capacity);
            List failures = new ArrayList(capacity);
            for (RunListener listener : currentListeners) {
                try {
                    notifyListener(listener);
                    safeListeners.add(listener);
                } catch (Exception e) {
                    failures.add(new Failure(Description.TEST_MECHANISM, e));
                }
            }
            fireTestFailures(safeListeners, failures);
        }

        abstract protected void notifyListener(RunListener each) throws Exception;
    }

    public void fireTestRunFinished(final Result result) {
        new SafeNotifier() {
            @Override
            protected void notifyListener(RunListener each) throws Exception {
                each.testRunFinished(result);
            }
        }.run();
    }
    public void fireTestAssumptionFailed(final Failure failure) {
        new SafeNotifier() {
            @Override
            protected void notifyListener(RunListener each) throws Exception {
                each.testAssumptionFailure(failure);
            }
        }.run();
    }
     private void fireTestFailures(List listeners,
            final List failures) {
        if (!failures.isEmpty()) {
            new SafeNotifier(listeners) {
                @Override
                protected void notifyListener(RunListener listener) throws Exception {
                    for (Failure each : failures) {
                        listener.testFailure(each);
                    }
                }
            }.run();
        }
    }

简单概括一下,就是调用它所管理的Listener的testRunFinished来处理Result,处理完毕之后就把该Listener标记为安全的,如果处理过程中出现异常,则将该异常加入failures列表,全部通知完毕后再次通知所有安全的Listener处理之前所有的Failure。这里还有一个问题,那就是Failure到底是怎样在运行时生成的。由于断言机制,所有的断言失败都会抛出对应的异常,对于断言异常,请看下文:

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

Fail会直接抛出原有的异常,再次调用RunLeaf

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

最后转入addFailure处理,而addFailure会最终通知Notifier中的各个Listener处理
。那JunitCore到底使用了怎样的Listener,它又执行了怎样的操作。

public Result run(Runner runner) {
        Result result = new Result();
        RunListener listener = result.createListener();
        notifier.addFirstListener(listener);
        try {
            notifier.fireTestRunStarted(runner.getDescription());
            runner.run(notifier);
            notifier.fireTestRunFinished(result);
        } finally {
            removeListener(listener);
        }
        return result;
    }

注意JunitCore的Run方法中加入了一个Result中内置的Listener,其定义如下

@RunListener.ThreadSafe
    private class Listener extends RunListener {
        @Override
        public void testRunStarted(Description description) throws Exception {
            startTime.set(System.currentTimeMillis());
        }

        @Override
        public void testRunFinished(Result result) throws Exception {
            long endTime = System.currentTimeMillis();
            runTime.addAndGet(endTime - startTime.get());
        }

        @Override
        public void testFinished(Description description) throws Exception {
            count.getAndIncrement();
        }

        @Override
        public void testFailure(Failure failure) throws Exception {
            failures.add(failure);
        }

        @Override
        public void testIgnored(Description description) throws Exception {
            ignoreCount.getAndIncrement();
        }

        @Override
        public void testAssumptionFailure(Failure failure) {
            // do nothing: same as passing (for 4.5; may change in 4.6)
        }
    }

可以看出该Listener在testFailure中完成了添加Failure的动作,看到这里我简直激动莫名,使用一个内置的Listener子类来避免显式为Result添加Failure,而是依然把这些操作集中在Notifier——Listener的观察者体系里,可谓精妙绝伦!

同时还需注意在runMain方法中加入了一个TextListener来完成打印结果的工作。

Junit的初始化错误处理

上面我们讲述了Junit如何处理样例中的断言错误以及运行时错误,但是当初始化Request的过程中一旦发生异常依然需要继续运行并提示测试失败,这又是怎么实现的呢?

我们回顾之前createRequest方法中的errorReport,代码如下:

    public static Request errorReport(Class klass, Throwable cause) {
        return runner(new ErrorReportingRunner(klass, cause));
    }

我们来看一下这个ErrorReportingRunner的实现

public class ErrorReportingRunner extends Runner {
    private final List causes;

    private final String classNames;

    public ErrorReportingRunner(Class testClass, Throwable cause) {
        this(cause, new Class[] { testClass });
    }
    
    public ErrorReportingRunner(Throwable cause, Class... testClasses) {
        if (testClasses == null || testClasses.length == 0) {
            throw new NullPointerException("Test classes cannot be null or empty");
        }
        for (Class testClass : testClasses) {
            if (testClass == null) {
                throw new NullPointerException("Test class cannot be null");
            }
        }
        classNames = getClassNames(testClasses);
        causes = getCauses(cause);
    }
    
    @Override
    public Description getDescription() {
        Description description = Description.createSuiteDescription(classNames);
        for (Throwable each : causes) {
            description.addChild(describeCause(each));
        }
        return description;
    }

    @Override
    public void run(RunNotifier notifier) {
        for (Throwable each : causes) {
            runCause(each, notifier);
        }
    }

    private String getClassNames(Class... testClasses) {
        final StringBuilder builder = new StringBuilder();
        for (Class testClass : testClasses) {
            if (builder.length() != 0) {
                builder.append(", ");
            }
            builder.append(testClass.getName());
        }
        return builder.toString();
    }

    @SuppressWarnings("deprecation")
    private List getCauses(Throwable cause) {
        if (cause instanceof InvocationTargetException) {
            return getCauses(cause.getCause());
        }
        if (cause instanceof InitializationError) {
            return ((InitializationError) cause).getCauses();
        }
        if (cause instanceof org.junit.internal.runners.InitializationError) {
            return ((org.junit.internal.runners.InitializationError) cause)
                    .getCauses();
        }
        return Arrays.asList(cause);
    }

    private Description describeCause(Throwable child) {
        return Description.createTestDescription(classNames, "initializationError");
    }

    private void runCause(Throwable child, RunNotifier notifier) {
        Description description = describeCause(child);
        notifier.fireTestStarted(description);
        notifier.fireTestFailure(new Failure(description, child));
        notifier.fireTestFinished(description);
    }
}

上面的大致逻辑依然是先生成Description(因为没有到ParentRunner的run那一步,Description没有生成,故需要在此生成),然后移交给Notifier处理

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

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

相关文章

  • Junit源码阅读(一)

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

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

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

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

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

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

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

    Little_XM 评论0 收藏0
  • Spring Boot快速入门():使用MyBatis(注解形式)进行数据库操作

    摘要:添加依赖新建项目选择三个依赖对于已存在的项目可以在加入,将会帮你自动配置好配置基本信息然后在下添加基本配置数据库连接地址数据库账号数据库密码数据库驱动创建实体创建一个实体,包含姓名年龄属性创建数据访问接口创建一个 添加依赖 新建项目选择web,MyBatis,MySQL三个依赖 showImg(https://segmentfault.com/img/bV2l1L?w=1684&h=1...

    lentoo 评论0 收藏0

发表评论

0条评论

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