Kotlin 测试¶
此文件使用 Kotlin 以及 Android/KMP 特定内容扩展 common/testing.md。
测试框架¶
- kotlin.test — 用于多平台(KMP)(
@Test、assertEquals、assertTrue) - JUnit 4/5 — 用于 Android 特定测试
- Turbine — 用于测试 Flow 和 StateFlow
- kotlinx-coroutines-test — 用于协程测试(
runTest、TestDispatcher)
使用 Turbine 测试 ViewModel¶
@Test
fun `loading state emitted then data`() = runTest {
val repo = FakeItemRepository()
repo.addItem(testItem)
val viewModel = ItemListViewModel(GetItemsUseCase(repo))
viewModel.state.test {
assertEquals(ItemListState(), awaitItem()) // 初期状態
viewModel.onEvent(ItemListEvent.Load)
assertTrue(awaitItem().isLoading) // ローディング中
assertEquals(listOf(testItem), awaitItem().items) // ロード完了
}
}
优先使用 Fake 而非 Mock¶
优先使用手写的 Fake,而不是 Mocking 框架:
class FakeItemRepository : ItemRepository {
private val items = mutableListOf<Item>()
var fetchError: Throwable? = null
override suspend fun getAll(): Result<List<Item>> {
fetchError?.let { return Result.failure(it) }
return Result.success(items.toList())
}
override fun observeAll(): Flow<List<Item>> = flowOf(items.toList())
fun addItem(item: Item) { items.add(item) }
}
协程测试¶
@Test
fun `parallel operations complete`() = runTest {
val repo = FakeRepository()
val result = loadDashboard(repo)
advanceUntilIdle()
assertNotNull(result.items)
assertNotNull(result.stats)
}
使用 runTest —— 它会自动推进虚拟时间,并提供 TestScope。
Ktor MockEngine¶
val mockEngine = MockEngine { request ->
when (request.url.encodedPath) {
"/api/items" -> respond(
content = Json.encodeToString(testItems),
headers = headersOf(HttpHeaders.ContentType, ContentType.Application.Json.toString())
)
else -> respondError(HttpStatusCode.NotFound)
}
}
val client = HttpClient(mockEngine) {
install(ContentNegotiation) { json() }
}
Room/SQLDelight 测试¶
- Room: 对于内存测试,使用
Room.inMemoryDatabaseBuilder() - SQLDelight: 对于 JVM 测试,使用
JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
@Test
fun `insert and query items`() = runTest {
val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)
Database.Schema.create(driver)
val db = Database(driver)
db.itemQueries.insert("1", "Sample Item", "description")
val items = db.itemQueries.getAll().executeAsList()
assertEquals(1, items.size)
}
测试命名¶
使用反引号包围的描述性名称:
@Test
fun `search with empty query returns all items`() = runTest { }
@Test
fun `delete item emits updated list without deleted item`() = runTest { }
测试结构¶
src/
├── commonTest/kotlin/ # 共有テスト(ViewModel、UseCase、Repository)
├── androidUnitTest/kotlin/ # Android ユニットテスト(JUnit)
├── androidInstrumentedTest/kotlin/ # インストルメンテッドテスト(Room、UI)
└── iosTest/kotlin/ # iOS 固有のテスト
最低限度的测试覆盖率:所有功能均需包含 ViewModel + UseCase。
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 ev/ECC