引言:为什么高级测试是可靠 Kotlin 代码的基础
在现代 Kotlin 开发中,编写代码只是工作的一半。真正的精通体现在创建可靠、可测试且易于维护的系统上。基本的单元测试每个人都熟悉,但当项目发展到微服务架构或复杂业务逻辑时,简单的检查就不够了。高级测试不仅包括类的隔离,还包括组件间交互的验证(集成测试)以及用户场景的完整复现(端到端测试)。
本文面向已经熟悉 Kotlin 基础知识并希望将项目质量提升到新水平的开发者。我们将使用现代工具:MockK、Testcontainers 和 Ktor Client,剖析测试的三个关键层级:单元测试、集成测试和端到端测试。您将学习如何编写不仅仅是“通过”的测试,而是真正保护您的代码免受回归影响的测试。
1. 单元测试:使用 MockK 进行隔离和模拟
单元测试是测试金字塔的基础。在高级层面,我们不仅检查函数,还使用模拟对象测试对象之间的复杂交互。在 Kotlin 中,最好的库是 MockK。
1.1. MockK 的高级功能
与 Mockito 不同,MockK 是专门为 Kotlin 编写的,支持协程、Lambda 表达式和扩展函数。让我们看一个测试使用存储库和协程的服务的示例:
// 待测试的服务
class UserService(private val repo: UserRepository) {
suspend fun getUserName(id: Long): String {
val user = repo.findById(id)
return user?.name ?: "Unknown"
}
}
// 测试
class UserServiceTest {
private val repo = mockk<UserRepository>()
private val service = UserService(repo)
@Test
fun `should return user name when user exists`() = runTest {
// given
coEvery { repo.findById(1L) } returns User(1, "Alice")
// when
val result = service.getUserName(1L)
// then
assertEquals("Alice", result)
coVerify(exactly = 1) { repo.findById(1L) }
}
@Test
fun `should return Unknown when user not found`() = runTest {
// given
coEvery { repo.findById(any()) } returns null
// when
val result = service.getUserName(99L)
// then
assertEquals("Unknown", result)
}
}
关键点:
coEvery和coVerify— 用于处理 suspend 函数。mockk()创建一个空的模拟对象,spyk()创建一个真实对象的部分模拟。- 使用
slots捕获参数。
1.2. 测试 Lambda 表达式和扩展函数
MockK 允许模拟扩展函数,这对于 Ktor 或 Spring WebFlux 等框架至关重要:
class StringValidator {
fun String.isValidEmail(): Boolean = this.contains("@")
}
// 测试
@Test
fun `test extension mock`() {
val validator = mockk<StringValidator>()
with(validator) {
every { "test@mail.com".isValidEmail() } returns true
every { "invalid".isValidEmail() } returns false
}
assertTrue("test@mail.com".isValidEmail())
assertFalse("invalid".isValidEmail())
}
2. 集成测试:数据库和 Testcontainers
集成测试检查您的代码如何与真实依赖项交互:数据库、消息队列、外部 API。这里 Testcontainers 派上了用场——一个在测试期间直接启动 Docker 容器的库。
2.1. 使用 PostgreSQL 配置 Testcontainers
对于 Spring Boot 应用程序,配置如下:
博客
书籍推荐
推荐阅读