Kotlin中的模拟对象与测试夹具:无痛测试指南

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:没有Mock的测试,就像没有咖啡因的咖啡?

\n\n

你好,同行!如果你在用Kotlin写代码,并且希望你的代码不会在每次提交时都崩溃,那么迟早你会接触到测试。而在测试中,就会用到mock对象和测试夹具。没有它们,单元测试就会变成集成测试:你测试的不是一个类,而是一整条依赖链。如果数据库宕机或API返回500错误,测试就会失败,尽管你的代码可能本身是完美的。

\n\n

在本文中,我们将探讨如何在Kotlin测试中正确使用Mock对象和测试夹具,使测试快速、可靠,并能告诉你真实的问题所在,而不是一句“全都坏了,去修吧”。我们开始吧!

\n\n

什么是Mock,为什么在Kotlin中需要它?

\n\n

Mock对象是对真实对象的模拟。我们不必启动整个服务器或数据库,而是创建一个“替身”,它的行为完全由我们控制。这使我们能够将待测试的代码与外部世界隔离开来。

\n\n

基本概念

\n
    \n
  • Mock — 一个我们可以控制其方法行为的对象。我们可以指定它返回什么,并验证它是否被调用过。
  • \n
  • Stub — Mock的一种特殊情况,它只返回预设的值,不验证调用情况。
  • \n
  • Fixture(测试夹具) — 执行测试所需的一组预先准备好的数据或状态。例如,一个测试用户或数据库配置。
  • \n
\n\n

在Kotlin世界中,有两个主要工具:MockK(Kotlin原生)和Mockito(在Java中很流行)。我们将使用MockK——它更好地支持协程、扩展函数和密封类。

\n\n

项目配置:添加MockK和JUnit 5

\n\n

首先,在build.gradle.kts中添加依赖:

\n\n
dependencies {\n    testImplementation("io.mockk:mockk:1.13.8")\n    testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n}\n
\n\n

现在可以编写测试了。我们创建一个简单的服务,并用mock来测试它。

\n\n

第一个示例:模拟Repository

\n\n

假设我们有一个UserService,它依赖于UserRepository:

\n\n
class UserService(private val repository: UserRepository) {\n    fun getUserName(id: Long): String {\n        val user = repository.findById(id)\n            ?: throw IllegalArgumentException("User not found")\n        return user.name\n    }\n}\n
\n\n

现在,我们使用mock对象来模拟Repository,编写测试:

\n\n
import io.mockk.every\nimport io.mockk.mockk\nimport io.mockk.verify\nimport org.junit.jupiter.api.Test\nimport kotlin.test.assertEquals\nimport kotlin.test.assertFailsWith\n\nclass UserServiceTest {\n\n    private val repository = mockk<UserRepository>()\n    private val service = UserService(repository)\n\n    @Test\n    fun `should return user name when user exists`() {\n        // 准备\n        every { repository.findById(1L) } returns User(1L, "Alice")\n\n        // 执行\n        val name = service.getUserName(1L)\n\n        // 断言\n        assertEquals("Alice", name)\n        verify { repository.findById(1L) }\n    }\n\n    @Test\n    fun `should throw exception when user not found`() {\n        // 准备\n        every { repository.findById(any()) } returns null\n\n        // 执行 & 断言\n        assertFailsWith<IllegalArgumentException> {\n            service.getUserName(99L)\n        }\n    }\n}\n
\n\n

请注意:every用于定义mock的行为,verify用于验证方法是否被调用。这是Kotlin测试中Mock对象和测试夹具的基础。

\n\n

测试夹具:提前准备数据

\n\n

当测试很多时,重复创建数据是一种不好的实践。这时测试夹具就派上用场了。在Kotlin中,可以方便地

博客

书籍推荐