摘要:的作用是包装从生成的逻辑,提供两种方案生成和。最后从生成也异常简单,也就是实现其方法返回该。
前言
尽管在第二次博客中我们讲述了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 ListcurrentListeners; 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 Listcauses; 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的工程结构 showImg(/img/bVsEeS); 从上图可以清楚的看出Junit大致分为几个版块,接下来一一简略介绍这些版块的作用。 runner:定义了Jun...
摘要:前言在这次的博客中我们将着重于的许多集成性功能来讨论中的种种设计模式。装饰器模式装饰器模式是为了在原有功能上加入新功能,在中绝对属于使用最频繁架构中最核心的模式,等都是通过装饰器模式来完成扩展的。 前言 在这次的博客中我们将着重于Junit的许多集成性功能来讨论Junit中的种种设计模式。可以说Junit的实现本身就是GOF设计原则的范例教本,下面就让我们开始吧。 装饰器模式 装饰器...
摘要:前言在建立的过程中,往往需要对当前的测试样例和注解进行验证,比如检查测试类是否含有非静态内部类,测试类是否是的。的验证机制非常精致而优美,在本次博客中我们就主要来谈一谈机制的实现。首先在中定义三个默认的类,如下。 前言 在建立Runner的过程中,往往需要对当前的测试样例和注解进行验证,比如检查测试类是否含有非静态内部类,测试类是否是Public的。Junit的验证机制非常精致而优美...
摘要:前言上次的博客中我们着重介绍了的机制,这次我们将聚焦到自定义扩展上来。在很多情形下我们需要在测试过程中加入一些自定义的动作,这些就需要对进行包装,为此提供了以接口和为基础的扩展机制。 前言 上次的博客中我们着重介绍了Junit的Validator机制,这次我们将聚焦到自定义扩展Rule上来。在很多情形下我们需要在测试过程中加入一些自定义的动作,这些就需要对statement进行包装,...
摘要:添加依赖新建项目选择三个依赖对于已存在的项目可以在加入,将会帮你自动配置好配置基本信息然后在下添加基本配置数据库连接地址数据库账号数据库密码数据库驱动创建实体创建一个实体,包含姓名年龄属性创建数据访问接口创建一个 添加依赖 新建项目选择web,MyBatis,MySQL三个依赖 showImg(https://segmentfault.com/img/bV2l1L?w=1684&h=1...
阅读 2941·2021-10-19 11:46
阅读 947·2021-08-03 14:03
阅读 2900·2021-06-11 18:08
阅读 2827·2019-08-29 13:52
阅读 2669·2019-08-29 12:49
阅读 440·2019-08-26 13:56
阅读 897·2019-08-26 13:41
阅读 773·2019-08-26 13:35