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中比较 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 框架核心结构 注解(Annotations) @Test @BeforeTest / @AfterTest @BeforeEach / @AfterEach @Ignore / @Test(expected) 断言函数(Assertions) assertEquals assertTrue / assertFalse assertNull / assertNotNull assertFailsWith 生命周期管理 beforeAll(init) beforeEach(setUp) afterEach(tearDown) afterAll(clean) 运行时引擎(JUnit Runner / Test Engine) 由 kotlin.test 自动集成,无需手动配置 核心原则 ① 一个 @Test 对应一个业务场景,失败时一目了然 ② 善用 @BeforeEach / @AfterEach 保证测试独立性

几句话总结本章重点

嗯,到这里你应该已经对 kotlin.test 的注解和断言有了基本概念。我再用一句话串一下:

  • 注解决定什么时候跑 —— 生命周期控制。
  • 断言决定跑得对不对 —— 结果验证。
  • 引擎负责真的跑起来 —— 你只需要关注前两层。

还记得我开头说的“轻量”吗?等你真正用起来就会发现,整个测试类写下来几乎没有什么模板代码。这就是kotlin.test的魅力——少写废话,多测核心。