3. kotlin.test框架简介:核心注解与断言函数归纳总结
说实话,刚开始用Kotlin写测试时,我也纠结过到底选哪个框架。
JUnit5?Spek?还是Kotlin官方自带的kotlin.test?
后来在几个回归测试项目里摸爬滚打一圈,我慢慢发现:kotlin.test 才是日常写单元测试最顺手的那把刀。它轻量、与Kotlin语法无缝衔接,而且不用额外引入一堆依赖。你想想看,一个testImplementation("org.jetbrains.kotlin:kotlin-test")就搞定了。
kotlin.test 到底提供了什么?
它不是从零造轮子,而是对JUnit核心能力的Kotlin封装。你用到的注解、断言函数,本质上跑在JUnit引擎上,但写法更符合Kotlin习惯。
核心注解——我的常用清单
我习惯把这些注解记成一张小表格,每次写新测试类直接对着看:
| 注解 | 作用 | 个人备注 |
|---|---|---|
@Test |
标记一个方法为测试用例 | 最常用,没有它测试跑不起来 |
@BeforeTest |
在整个测试类所有用例执行前运行一次 | 适合初始化共享资源,比如打开数据库连接 |
@AfterTest |
在整个测试类所有用例执行后运行一次 | 用来清理全局状态,我经常在这里关闭文件流 |
@BeforeEach |
每个 @Test 方法之前都会执行 |
重置mock对象、初始化测试数据的好地方 |
@AfterEach |
每个 @Test 方法之后都会执行 |
我习惯用它清空内存缓存,防止用例间污染 |
@Ignore |
跳过该测试方法(或整个测试类) | 配合 reason 参数写清楚为什么跳过,不然回头看一脸懵 |
@Test(expected = Exception::class) |
预期方法会抛出指定异常 | 注意这是JUnit4风格,Kotlin里我更喜欢用 assertFailsWith |
小技巧: 如果你用
@BeforeTest + @AfterTest 管理重量级资源,千万别在里面创建对象给 @Test 用——因为JUnit会为每个测试方法新创建一个测试类实例,@BeforeTest 里的变量没法跨方法传递。我踩过一次坑,后来全换成 @BeforeEach + companion object。断言函数——比JUnit更清爽
kotlin.test 提供了一套极度简化的断言API。
它们都是顶层函数,直接调用就行,不用写 Assertions.assertEquals 那套长串。
assertEquals(expected, actual)—— 比较值相等。支持所有类型。assertNotEquals(illegal, actual)—— 不想看到某个值时用。assertTrue(condition)/assertFalse(condition)—— 布尔判断。assertNull(value)/assertNotNull(value)—— 空值检查。assertFailsWith<T : Throwable>(block: () -> Unit)—— 捕获异常并返回异常对象,可以继续验证消息。assertContentEquals(expectedArray, actualArray)—— 数组/列表内容比较。fail(message)—— 直接让测试失败,用于分支条件里。
⚠️ 我曾经遇到的陷阱:
在Kotlin中比较
正确的做法是用带
在Kotlin中比较
Double 或 Float 时,直接用 assertEquals 可能因为精度问题失败。正确的做法是用带
delta 参数的重载版:assertEquals(0.1 + 0.2, 0.3, 1e-6)
第一个参数是期望值,第二个是实际值,第三个是允许误差。记得别把顺序搞反了——我当年搞反三次才记住。
一个完整的测试例子
下面这个测试类覆盖了我刚刚说的所有常用注解和断言:
import kotlin.test.*
class CalculatorTest {
private lateinit var calculator: Calculator
@BeforeEach
fun setUp() {
calculator = Calculator()
println("每个测试前都会重置计算器")
}
@AfterEach
fun tearDown() {
println("每个测试后执行清理")
// 比如清空一些临时文件
}
@Test
fun `加法应该返回正确结果`() {
val result = calculator.add(3, 5)
assertEquals(8, result)
}
@Test
fun `除以零应该抛异常`() {
val exception = assertFailsWith<IllegalArgumentException> {
calculator.divide(10, 0)
}
assertTrue(exception.message?.contains("除数不能为零") == true)
}
@Ignore("这个功能还没实现,等下一版再测")
@Test
fun `乘法测试待完成`() {
fail("还没写实现呢")
}
}
你看,@BeforeEach 和 @AfterEach 把每个用例的前后逻辑包得干干净净。我个人习惯把测试方法名写成反引号括起来的自然语言,可读性真的很好——跑测试失败时一眼就能看到是哪个业务场景出了问题。
kotlin.test 的知识体系
下面这张图总结了我对 kotlin.test 框架的理解。它其实就三层:注解层、断言层和底层的JUnit引擎。你不需要关心引擎细节,但注解和断言必须熟练。
几句话总结本章重点
嗯,到这里你应该已经对 kotlin.test 的注解和断言有了基本概念。我再用一句话串一下:
- 注解决定什么时候跑 —— 生命周期控制。
- 断言决定跑得对不对 —— 结果验证。
- 引擎负责真的跑起来 —— 你只需要关注前两层。
还记得我开头说的“轻量”吗?等你真正用起来就会发现,整个测试类写下来几乎没有什么模板代码。这就是kotlin.test的魅力——少写废话,多测核心。