[译]JUnit5 整合Kotlin
文章目录
本文翻译自JUnit 5 With Kotlin for Java Developers。
在本教程中将学习在Java
和Kotlin
中编写JUnit 5
测试的差异,同时也将学习如何在Gradle Kotlin DSL
1在构建脚本中配置JUnit 5
环境。
本文是JUnit 5 教程的一部分。
相关视频
如果你喜欢通过视频学习,可以查看Youtube
中相关的学习视频。
配置
我们使用Groovy DSL
将传统的Gradle
构建脚本编写为build.gradle
文件。Gradle Kotlin DSL
对多项改进提供了替代语法,如内容辅助和重构,通过Kotlin DSL
编写的构建脚本被命名为build.gradle.kts
。要在Kotlin
中编写JUnit 5
测试,首先需要在build.gradle.kts
中添加junit-jupiter
坐标并且告知其在测试中使用JUnit
平台。
|
|
基本功能
JUnit 5
中的大部分功能在Kotlin
中都能像在Java
中一样正常工作,一切都是开箱即用的。
一个显著的不同是如何在测试报告或IDE中自定义展示测试方法的名称。
在Java
中,我们可使用@DisplayName
注解来让方法名可读,但在Kotlin
中我们给变量和方法添加反引号来表示。
|
|
利用反引号让代码和测试结果更具有可读性,通常我们不应该使用这样的方法名称吗,但它对此需求实现很方便。
惰性评估
JUnit 5
通过lambda对错误消息提供了惰性评估,利用lambda可避免构建不必要的昂贵错误消息。
在Kotlin
中,如果一个函数的最后一个参数接收一个函数,就能重写为括号外的lambda表达式。
|
|
相对于Java
,此种实现方式让在Kotlin
中编写断言时的语法更简洁。
断言
JUnit 5
中的任何断言都能在Kotlin
中正常工作,然而有几种Kotlin
特定的断言方法更适合该语言,这些断言方法是org.junit.jupiter.api
包中的顶级函数。
下面是一个代码抛出异常的断言示例,在Java
中,我们可通过在assertThrows()
调用中传入一个lambda表达式,在Kotlin
中,同样可通过在断言调用后添加一个lambda表达式让其更具有可读性。
|
|
这个简单的lambda语法同样适用于分组断言,通过分组断言可实现同一时刻执行多个断言并且一并反馈失败。
就像在Java
中,我们可在Kotlin
的assertAll()
调用中编写lambda表达式,但该语法不太冗长。
|
|
相比较Java
,此种方式不太冗长同时更具有可读性。
参数化测试
在JUnit 5
中有多种方式编写参数化测试,它们中的大部分可在Kotlin
中无需修改直接运行。
需要考虑的一点是在使用@MethodSource
注解时有一些不同,此注解在类中接收一个静态方法作为参数来源。
要在Kotlin
中实现同样功能,我们需要创建一个伴生对象并给相关方法添加@JvmStatic
注解,此注解将使该方法以Java
静态方法的形式存在。
|
|
若没有添加@JvmStatic
注解将会出现如下错误:
|
|
利用此种方式实现参数化测试虽然能正常工作但却没有Java
中的方便,另一个需要注意的是每个类中只能有一个伴生对象,因此所有需要提供参数的方法需要组织在一起。
补充阅读
动态测试
JUnit 5
引入了一种新的编程模型,允许我们通过@TestFactory
注解注释的工厂方法在运行时生成动态测试。
通常我们会提供一个DynamicTest
示例的集合,以前述的计算器为例:
|
|
每个动态测试都会显示为自己的测试,但这看起来不干净且有一些重复。
再次,此处有一种方式让其更具有可读性,我们可用一些应映射函数来消除这些重复。··
|
|
此种方式除了语法略有不同,与我们在参数化测试种的实现很相似。
嵌套测试
JUnit 5
中的嵌套测试允许我们给测试定义层级结构,同Java
中类似,我们或许希望下述代码能正常运行:
|
|
上述例子会给我们一个警告:只有非静态的嵌套类才能作为@Nested
测试类,JUnit 5
找不到任何可执行的测试方法。
默认情况下,Kotlin
中的嵌套类与Java
中的静态类很像,只有非静态嵌套类(如内部类)才能添加@Nested
注解作为测试类。
解决方案为将对应类在Kotlin
中标记为inner class
:
|
|
现在JUnit 5
能够发现内部嵌套的测试类以及对应的测试方法。
补充阅读
静态方法和属性
我们已经简要介绍了静态方法和Kotlin
,要让Kotlin
中的方法看起来像一个Java
静态方法,我们需要创建一个伴生对象并给对应方法上添加@JvmStatic
注解。
|
|
另一个可能的陷阱是在使用静态属性时,在Java
中可通过添加static
属性来实现,因此如果你刚接触Kotlin
,或许会期望类似下述代码能够执行:
|
|
但是,如果我们编写类似的代码并执行,将会看见一个关于属性为私有的错误消息:
|
|
解决方案为给该属性添加@JvmField
注解:
|
|
添加上该注解后会暴露该Kotlin
属性作为一个Java
属性并且JUnit 5
能够发现它。
生命周期方法
JUnit 5
中的生命周期方法同样能在Kotlin
中运行。
然而,添加了@BeforeAll
和@AfterAll
注解的方法在默认情况下需要为静态方法,原因是JUnit 5
为每个测试方法创建一个测试实例,并且没有其他方法可以在所有测试之间共享状态。
幸运的是,在JUnit 5
中可通过添加@TestInstance(Lifecycle.PER_CLASS)
注解让每个测试类只创建一个测试实例,通过对生命周期更改消除了对静态方法的要求。
|
|
由于现在测试方法见共享实例状态,如果测试方法依赖于存储在实例间的状态,或许需要在@BeforeEach
或@AfterEach
注解方法中重置状态,一般来说,尽量避免编写依赖于这种状态的测试。
补充阅读
可重复注解
当前Kotlin
并不支持重复注解,因此使用多个扩展或标签进行测试比 Java 稍微复杂一些。
譬如,在Java
中我们可重复添加@Tag
注解来给某个测试添加多个标签:
|
|
然而在Kotlin
中我们不能同时有多个@Tag
注解,相反必须使用@Tags
注解来包装重复的标签。
|
|
当有多个扩展时也是类似,因此我们不能直接使用多个@ExtendWith
,相反必须使用@Extensions
注解来包装重复的扩展。
|
|
总结
尽管在某些场景下有些语法与Java
中的不同,大部分JUnit 5
的功能特性能在Kotlin
中完美运行,然而,由于Kotlin
语言的工作方式,我们通常可以使代码更具可读性。
本文的示例代码能在GitHub中找到。
-
DSL
即Domain Specific Language
,中文译领域特定语言
,专门用于解决特定领域问题而设计开发的。 ↩︎