博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JUnit 5 – 早期试用体验 – 第2篇
阅读量:6403 次
发布时间:2019-06-23

本文共 9830 字,大约阅读时间需要 32 分钟。

\

主要结论

\\
  • JUnit 5就要来了!\\t
  • 其中包含改进的API和扩展模型将大幅完善“JUnit工具”。\\t
  • 模块化的体系结构使得“JUnit平台”可以用于其他测试框架。\\t
  • 虽然经过了彻底重写,但可在同一个代码基中与老版本Junit共存。\
\\

在,我们看了如何设置JUnit和开始编写测试,以及看到了表面上发生的变化。我们也讨论了重写的必要性以及新架构是如何划分出Junit Platform和JUnit Jupiter的。

\\

在这第二篇,我们将会仔细的看看如何运行测试和JUnit5带给我们开发者的一些非常酷的新特性。

\\

运行测试

\\

JUnit Jupiter测试能够作为JUnit4的一部分运行或者跑在JUnit5基础设施上。

\\

作为JUnit4的一部分

\\

还记得那5秒的设置么?使用下面的内容:

\\
\org.junit.jupiter:junit-jupiter-api\org.junit.jupiter:junit-jupiter-engine\org.junit.platform:junit-platform-runner\
\\

正如我们第一篇所说的那样,我们需要junit-jupiter-api去编写我们的单元测试,需要junit-jupiter-engine去发现和运行它们。最后那个,junit-platform-runner包含JUnit4 Runner接口的实现类JUnitPlatform,该类仅是简单地通知引擎运行测试。使用@RunWith(JUnitPlatform.class)标注以JUnit4的一部分运行我们的测试。需要注意的是,这样能够正常工作的类必须是常规的JUnit4测试类。例如,它必须符合你选择的工具的约定(像Maven的)。使用这种方式,JUnit Jupiter测试将能够跑在任何集成了JUnit4的工具中。

\\

然而这里我们能够做小的改进。执行器能够识别@SelectPackages注解,从而用来运行某个包下的所有测试。

\\
\package com.infoq.junit5;\\import org.junit.platform.runner.JUnitPlatform;\import org.junit.platform.runner.SelectPackages;\import org.junit.runner.RunWith;\\@RunWith(JUnitPlatform.class)\@SelectPackages({ \"com.infoq.junit5\" })\public class TestWithJUnit5 { }\
\\

在这里,包被解析成一种层次结构,该结构表示所有以com.infoq.junit5开头的包中的测试都将被运行。因为这些类都是被JUnit Platform运行器自动发现的(替代了JUnit4集成工具),所以这些类都不再必须是public的。

\\

警告:如果我们使用了利用该新架构的机制(我们马上就会讨论这个)并且该机制包含JUnit4的引擎,这些测试将会被执行两次:一次在JUnit4运行的时候(由于我们应用了@RunWith的执行器),另外一次在JUnit5运行的时候。

\\

使用JUnit5

\\

现在,让我们看看如何让全套JUnit5机制运行起来。我们可以使用junit-platform-launcher提供的API,然后绑定若干个引擎(对于现在可能是JUnit4和5)去发现和运行测试。

\\

集成开发环境

\\

IntelliJ IDEA自从版本就有了基础的。虽然这还不完美,因为这相当于在追逐一个移动的目标,但是这就使新的JUnit更容易使用了。

\\

Eclipse团队也正在,因此估计时间不会太久。

\\

构建工具支持

\\

JUnit团队本身已经在构建工具支持上努力工作;初步的Gradle插件和Maven Surefire provider已经投入使用,一旦社区准备好接受它们,这两个项目都计划移交给各自的社区。

\\

和都有示例项目。关于更多的细节可以查看。

\\

命令行必胜!

\\

如果你不喜欢IDE和构建工具,你可以尝试,该功能允许你可以直接在命令行启动测试。要得到该功能你需要。使用该功能最方便的方式是将junit-jupiter-api和junit-jupiter-engine内容放入lib目录,然后编辑class path的定义脚本,定义CLASSPATH=$APP_HOME/lib/*。

\\

你可以如下这么使用:

\\
\# run all tests\junit-platform-console -p ${path_to_compiled_test_classes} -a\\# run a specific test\junit-platform-console\    -p ${path_to_compiled_test_classes}\    org.infoq.junit5.HelloWorldTest\
\\

如果你还有其他依赖,例如Mockito之类的其他测试库,把它们加入到class path,在-p后面列出它们。

\\

闪亮的新特性

\\

我们已经了解了JUnit的新架构,如何基于现有的工具支持去设置它。相比于过去,这将改善我们编写测试的API。接下去,让我们转而去看它带给我们的新特性。

\\

嵌套测试

\\

JUnit Jupiter使得编写嵌套测试类完全不费力,意味着你可以用BDD(行为驱动开发)的风格组织测试类。你只需要在内部类上加注解@Nested。

\\
\class NestedTest {\\    int count = Integer.MIN_VALUE;\\    @BeforeEach\    void setCountToZero() {\     count = 0;\    }\\    @Test\    void countIsZero() {\     assertEquals(0, count);\    }\\    @Nested\    class CountGreaterZero {\\     @BeforeEach\     void increaseCount() {\         count++;\     }\\     @Test\     void countIsGreaterZero() {\         assertTrue(count \u0026gt; 0);\     }\\     @Nested\     class CountMuchGreaterZero {\\         @BeforeEach\         void increaseCount() {\             count += Integer.MAX_VALUE / 2;\         }\\         @Test\         void countIsLarge() {\             assertTrue(count \u0026gt; Integer.MAX_VALUE / 2);\         }\     }\   }\ }\
\\

生命周期的方法@BeforeEach和@AfterEach在这里也能工作,按照由外到内的顺序执行。这样就可以增量构建用于内部测试的上下文了。

\\

为了充分利用该设置,重点在于内部类必须拥有访问外部测试类字段的权限。这就要求内部类必须不是静态的。因此静态方法是禁止使用的,所以@BeforeAll和@AfterAll在这种情况下就无法使用了。

\\

在我们介绍完另一个新特性后,我们将会看到嵌套的测试结果是如何显示的,该特性和@Nested可以很好地配合。

\\

命名测试

\\

开发人员经常让测试的名称能够表达测试的前置条件、被测试的单元甚至是预期的行为。在一个方法名中满足这些需求会令它变得非常笨拙。

\\

JUnit带来了这个问题的一种解决方案。新的注解@DisplayName接受任意的字符串,被JUnit用来作为类或者方法的显示名。JUnit团队经常给出以下示例:

\\
\@DisplayName(\"A stack\")\class TestingAStack {\\    @Test\    @DisplayName(\"is instantiated with new Stack()\")\    void isInstantiatedWithNew() { /*...*/ }\\    @Nested\    @DisplayName(\"when new\")\    class WhenNew {\\        @Test\        @DisplayName(\"is empty\")\        void isEmpty() { /*...*/ }\\        @Test\        @DisplayName(\"throws EmptyStackException when popped\")\        void throwsExceptionWhenPopped() { /*...*/ }\\        @Test\        @DisplayName(\"throws EmptyStackException when peeked\")\        void throwsExceptionWhenPeeked() { /*...*/ }\\        @Nested\        @DisplayName(\"after pushing an element\")\        class AfterPushing {\\            @Test\            @DisplayName(\"it is no longer empty\")\            void isEmpty() { /*...*/ }\\            @Test\            @DisplayName(\"returns the element when popped and is empty\")\            void returnElementWhenPopped() { /*...*/ }\\            @Test\            @DisplayName(\                    \"returns the element when peeked but remains not empty\")\            void returnElementWhenPeeked(){ /*...*/ }\    }\}\
\\

组合使用@Nested和@DisplayName可以创造出易读的输出,为从事行为驱动开发的人带来快乐。

\\

2b60cf55de3b26ba6c90d16d3c9e04b1.jpg

\\

参数注入

\\

在以前,测试方法不允许带参数。那是有道理的,毕竟JUnit可能传递什么给它们呢?在版本5中,团队回答了这个问题就是“任何你想传的!”。

\\

因此,现在测试方法可以有参数了。对于每一个参数,JUnit将会搜索一个扩展来提供值。有两个这样的扩展是内置的,能够被用来注入TestInfo和TestReporter,但这两个扩展在日常测试编写中并不常用。

\\

更有趣的是MockitoExtension,该扩展会注入一个mock到所有以@InjecMock注解的参数。这显示了虽然扩展API仍然在开发中,但是已经能够被善加利用了。

\\

因此,让我们了解一下吧。

\\

可扩展性

\\

JUnit Lanmda项目有几个核心原则,其中一个就是“扩展点优于特性”。这是个伟大的原则,JUnit5看上去将会很好的实现该原则。

\\

自定义注解

\\

所有的JUnit注解都能够被当作元注解使用。也就是说,它们可以用来标注其他注解。Jupiter引擎预料到了这种情况,能够接受这些元注解就像它们直接标注在对应的元素上一样。

\\

有了这个,我们很轻松就能创建出被JUnit Jupiter完全支持的自定义注解。

\\
\/**\ * We define a custom annotation @IntegrationTest that:\ * - stands in for '@Test' so that the method gets executed\ * - has the tag \"integration\" so we can filter by that,\ *   e.g. when running tests from the command line\ */\@Target({ElementType.TYPE, ElementType.METHOD})\@Retention(RetentionPolicy.RUNTIME)\@Test\@Tag(\"integration\")\public @interface IntegrationTest { }\
\\

我们可以这样使用:

\\
\@IntegrationTest\void runsWithCustomAnnotation() {\    // this is run even though `@IntegrationTest` is not defined by JUnit\}\
\\

真棒!一个简单而贴心的特性,将对扩展产生巨大的影响。

\\

扩展点

\\

JUnit定义了能够用于注入行为的特定扩展点。让我们看看当前已经定义的扩展点:

\\

TestInstancePostProcessor

\\

能够执行条件计算,该结果决定是否执行测试。举个例子,比如只有在指定的操作系统或者外部的资源有效的时候才运行测试。

\\

BeforeAll, BeforeEach, AfterEach, AfterAll

\\

在测试执行前或者执行后立即执行。

\\

ParameterResolver

\\

该扩展点在测试抛出异常结束测试的时候被执行。它可以用来在出错的时候回滚数据库事务或者吞掉预期的异常。

\\

这些扩展点都有一个接口。每个接口都定义了方法,这些方法会在适当的时候被Jupiter引擎调用,同时传入相应的上下文到扩展点(比如测试实例,测试方法,参数,当前的注解等)。

\\

为了实际的应用某个扩展,测试类或者方法必须加上注解@ExtendWieh(OurNewExtension.class)。

\\

让我们看两个例子亲身感受下这个是如何工作的。如果有兴趣了解更多,参看Rüdiger Herrmann的文章,并自己进行实验。

\\

条件

\\

@Disabled

\\

我们已经看到使用@Disable注解很简单就能把测试禁用了。让我们看看这是怎么实现的。

\\

DisabledCondition类实现了接口TestExecutionCondition和ContainerExecutionCondition。相应的方法被调用的时候带有上下文,该上下文能够用来检查@Disabled注解是否存在。如果存在,方法就会返回一个值表明该测试被禁用。

\\

见代码:

\\
\@Override\public ConditionEvaluationResult evaluate(\        ContainerExtensionContext context) {\    return evaluate(context.getElement());\}\\@Override\public ConditionEvaluationResult evaluate(\        TestExtensionContext context) {\    return evaluate(context.getElement());\}\\private ConditionEvaluationResult evaluate(\        Optional\u0026lt;AnnotatedElement\u0026gt; element) {\    Optional\u0026lt;Disabled\u0026gt; disabled =\            findAnnotation(element, Disabled.class);\    if (disabled.isPresent()) {\     String reason = /* … */;\     return ConditionEvaluationResult.disabled(reason);\    }\    return ENABLED;\}\
\\

现在我们知道DisabledCondition扩展类负责实际实现@Disabled想要的行为。那么为什么这里我们不使用@ExtendWith(DisabledCondition.class)来禁用测试?

\\

在该注解外,还有一个扩展注册中心,那里包含内置的扩展来减少开销。尽管还有另外一种,稍微有一点点迂回的方式来做这个事情。我们现在就用这个来实现我们自己的条件注解。

\\

@Available

\\

让我们假设有一个集成测试,它依赖于REST服务当前是否有效。当远程终结点挂掉的时候需要禁用这些测试。我们创建注解@Available,接收一个字符串作为值,这样我们就可以按照下面的方式使用:

\\
\@Test\@Available(“https://www.appdynamics.com”)\void testAuthorSearch() {\    // the test\}\
\\

接着,我们创建名为AvailableCondition的类,该类和上面的很像。该类实现和上面一样的接口(因此我们能够在测试类和独立的方法上使用该注解)并将两个方法调用传递给私有的evaluate方法,该方法这里只需要已注解的元素。

\\

如果注解存在,它会取出URL里面的值然后做一次测试调用。只有当调用符合要求的时候对应的测试才会被执行。

\\

现在,我们需要做的就是通知JUnit,让它知道AvailableCondition这个类。我们可以在所有的这类测试上加上注解@ExtendWith(AvailableCondition.class),当然这样做太没意思了。现在让我们来看一下我提到过的小技巧。还记得元注解以及JUnit如何寻找它们么?我们能够在这里使用并给注解@Available加上我们的扩展。JUnit将会发现并且立即应用它:

\\
\@Retention(RetentionPolicy.RUNTIME)\@ExtendWith(AvailableCondition.class)\public @interface Available {\    String value();\}\
\\

我喜欢这个。

\\

注入

\\

让我们回到上文提到过的,然后看看它是如何工作的。它实现了ParameterResolver接口。该接口包含两个方法定义:

\\
  • 第一个是supports,用来判断resolver是否支持对应的参数。\\t
  • 第二个是resolve,需要返回将会注入的实例。\

我们只注入带有注解的参数,这看上去很合理。因此我们这样实现:

\\
\@Override\public boolean supports(\        ParameterContext parameterContext,\        ExtensionContext extensionContext) {\    return parameterContext\                .getParameter()\                .isAnnotationPresent(InjectMock.class);\}\
\\

使用Mockito创建Mock易如反掌:

\\
\@Override\public Object resolve(\        ParameterContext parameterContext,\        ExtensionContext extensionContext) {\    Class\u0026lt;?\u0026gt; parameterType = parameterContext().getParameter().getType();\    return Mockito.mock(parameterType);\}\
\\

(实际的实现会略微复杂点,因为它允许在生命周期的方法中设置mock,这意味着它必须跟踪mock实例而不是总创建新的实例。但是这是最基本的模式。)

\\

我们能够使用类似的手段,用来注入配置服务或者其他有意义的对象到我们的测试里。

\\

总结

\\

到此,我们关于JUnit5的深度了解就要结束了,让我们慢慢地重新回顾一下。

\\

我们已经知道JUnit4的扩展机制存在问题,它本身也缺乏模块化,需要完全的重写才能继续向前发展。在2015年,一个由经验丰富的开发人员组成的团队收集并整理了这些想法,由他们的老板发起,一大群人投入了几个月就开发出了原型(即alpha版),以及最近的里程碑1和2。

\\

JUnit的架构分化出“JUnit平台”和“JUnit工具”两个部分。JUnit Platform就是前者的实现,这是一个用来组织各种不同引擎的启动器。目前,已经有引擎为JUnit4(JUnit Vintage)和JUnit5做了实现。不过在将来,所有测试框架都可以提供它们自己的引擎。每个引擎都可以运行基于它们自己特定API的测试。架构的另外一个部分是JUnit Jupiter,也就是“JUnit工具”。该工具是开发人员用来编写测试的,它由只包含API的JAR包组成,非常的精简。

\\

JUnit4用来实现扩展性的手段是运行器和规则,这些将会被扩展点代替。扩展点存在于JUnit Jupiter生命周期的各个阶段中,从测试实例的创建到条件执行,以及异常处理。

\\

在巨大改变的新架构和扩展模型之上的是闪亮的新特性层:测试能够被简单的命名和嵌套,并能被注入参数。再上面的层,变化就比较小了。Jupiter由可见级别为同个包内的类和方法组成,只是稍微重命名了生命周期注解以及增量改进了断言和假说。

\\

一瞥之下甚至看不出这两个版本的区别。

\\

那么接下来会发生什么?我们可以基于我们的代码去查看里程碑2,去思考那些可能不太容易表达的方面。这些案例是JUnit团队特别感兴趣的!

\\

他们按顺序,继续改进项目。最近他们刚完成的特性是,该功能允许运行时创建测试,可以解锁备受期待的lambda测试功能。计划上是再发布另外一个里程碑,甚至可能在今年年底发布final版本。

\\

我已经等不及了!

\\

acea5e4678d5c49aa3c16288f6e021fa.jpgNicolai Parlog是一位软件开发者兼Java传教士。他会经常阅读、思考并撰写有关Java的文章,在以写代码为生的同时也享受着写代码的乐趣。他是多个开源项目的长期贡献者,并维护了一个有关软件开发的博客:。你也可以在关注Nicolai。

\\

查看英文原文:

\\

感谢对本文的审校。

\\

给InfoQ中文站投稿或者参与内容翻译工作,请邮件至。也欢迎大家通过新浪微博(,),微信(微信号:)关注我们。

转载地址:http://oqnea.baihongyu.com/

你可能感兴趣的文章
Flutter 云音乐
查看>>
RecyclerView实现多type页面
查看>>
个人的web商城网站
查看>>
debian fcitx
查看>>
排中律与实无穷问题的性质分析
查看>>
08/23 学习总结
查看>>
关于Ubuntu下安装phpmyadmin后mysqli丢失的解决
查看>>
物理层
查看>>
linux多网卡路由设置
查看>>
win7环境下的栈溢出与实战
查看>>
查看ios字体库方法
查看>>
八大监听器
查看>>
self.navigationController退出到指定页面,或者一次性pop出n个页面
查看>>
Quartz实现数据库动态配置定时任务
查看>>
iptables 端口转发以及双向通信
查看>>
备战一线互联网公司Java工程师面试题 (1)
查看>>
ThinkPHP中自动验证失败
查看>>
jquery图片切换插件jquery.cycle.js参数详解
查看>>
JavaScript push() 方法
查看>>
Map集合
查看>>