ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

ECC 规则实践:Kotlin 与 Android/KMP 测试体系实战指南

2026/9/10 0:56:59 拓冰建站 浏览量
ECC 规则实践:Kotlin 与 Android/KMP 测试体系实战指南 ECC 规则实践Kotlin 与 Android/KMP 测试体系实战指南【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC本文基于 docs/ja-JP/rules/kotlin/testing.md对应英文原版 rules/kotlin/testing.md编写并融合 ECC 仓库中的公共测试规则、Kotlin 架构模式与协程技能源码级佐证。本文是 ECCAgent Harness Performance Optimization System为 Kotlin 及 Android/KMP 项目制定的测试规范实战指南。它继承自 ECC 公共测试规则 rules/common/testing.md围绕「测试框架选型、ViewModel 与 Flow 测试、Fake 优先、协程测试、Ktor MockEngine、数据库测试、命名与目录组织」展开并给出可直接复制运行的 Kotlin 代码示例。读完本文你将掌握一套覆盖单元测试到 KMP 多平台测试的完整落地方案并理解它与 TDD 工作流、tdd-guideAgent 之间的协作关系。一、规则来源与适用场景testing.md属于 ECC 仓库rules/kotlin/目录下的五份 Kotlin 专项规则之一其余为coding-style.md、patterns.md、hooks.md、security.md。其文件头通过paths字段声明适用范围--- paths: - **/*.kt - **/*.kts ---也就是说凡是仓库内新增或修改的 Kotlin 源文件.kt与构建脚本.kts都应当遵循该测试规范。文档开篇明确说明「このファイルは common/testing.md を Kotlin および Android/KMP 固有のコンテンツで拡張します」本文件以 Kotlin 及 Android/KMP 专属内容扩展公共测试规则。公共规则位于 rules/common/testing.md两者是「通用基线 语言专项」的叠加关系下文会分别展开。二、测试框架选型四个层次的组合规则规定了四种测试框架各司其职框架用途关键 APIkotlin.test多平台KMP共享测试Test、assertEquals、assertTrueJUnit 4/5Android 专属单元测试Test、BeforeEach、断言 APITurbineFlow 与 StateFlow 的异步流断言test { }、awaitItem()kotlinx-coroutines-test协程测试runTest、TestDispatcher、advanceUntilIdle()选择逻辑很清晰KMP 的commonTest中依赖平台无关的kotlin.testAndroid 平台测试使用 JUnit凡是涉及Flow/StateFlow/SharedFlow的异步数据流一律借助 Turbine 的test扩展逐帧断言而所有涉及suspend函数的测试统一在runTest提供的虚拟时间环境中执行。这四者不是互斥选项而是按被测对象的性质组合使用。三、最低测试覆盖与 TDD 基线继承自公共规则Kotlin 专项规则在文末给出硬性要求所有功能feature必须至少覆盖 ViewModel UseCase 两层测试原文「最低限のテストカバレッジ: すべての機能に対して ViewModel UseCase」。这一要求是公共测试规则在 Kotlin 层的落地。ECC 的 rules/common/testing.md 规定整体覆盖率基线为80%并明确三类测试缺一不可单元测试函数/工具/组件、集成测试API 端点、数据库操作、端到端测试关键用户流程。其配套的TDD 强制工作流为先写测试RED运行测试——应当失败编写最小实现GREEN运行测试——应当通过重构IMPROVE验证覆盖率80%同时建议在测试失败时按序排查使用tdd-guideAgent → 检查测试隔离性 → 核对 mock 是否正确 → 修复实现而非测试除非测试本身有误。公共规则还推荐测试内部采用AAA 三段式结构Arrange-Act-Assert便于读者快速定位「准备数据 / 触发动作 / 校验结果」三个环节。四、ViewModel 测试Turbine 逐帧断言状态流Kotlin 专项规则给出第一个核心示例——用 Turbine 测试以StateFlow驱动的 ViewModelTest 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) // 加载完成 } }这段测试之所以成立前提是被测 ViewModel 遵循了 rules/kotlin/patterns.md 定义的ViewModel 模式单一不可变状态对象data class ScreenState、事件入口onEvent、单向数据流内部MutableStateFlow暴露为asStateFlow()。测试逐一awaitItem()把「初始态 → 加载中 → 加载完成」三个状态按顺序断言恰好验证了单向数据流的状态迁移时序。更复杂的多 Flow 组合场景如combine多个数据源同样可以用 Turbine 断言最终合并结果详见 skills/kotlin-coroutines-flows/SKILL.md 中「Testing StateFlow with Turbine」一节——那里展示了onSearch(query)触发后依次断言 loading 态与 loaded 态的完整写法。五、Fake 优先于 Mock手写替身的正确姿势规则明确优先使用手写 Fake而非 Mock 框架原文「モッキングフレームワークよりも手書きのフェイクを優先する」。示例class FakeItemRepository : ItemRepository { private val items mutableListOfItem() var fetchError: Throwable? null override suspend fun getAll(): ResultListItem { fetchError?.let { return Result.failure(it) } return Result.success(items.toList()) } override fun observeAll(): FlowListItem flowOf(items.toList()) fun addItem(item: Item) { items.add(item) } }这个 Fake 与 rules/kotlin/patterns.md 中定义的ItemRepository接口严格对应suspend函数返回ResultT可用fetchError注入失败路径Flow承载响应式数据流。Fake 的价值在于它是一段真实的、可读的实现能随接口演化同步修改行为完全由测试代码掌控不会出现 Mock 框架常见的「过度打桩导致测试与实现脱钩」问题。在 skills/kotlin-coroutines-flows/SKILL.md 的「Faking Flows」一节还给出了基于MutableStateFlow的可发射型 Fake——通过emit(items)主动推送数据配合 Turbine 断言流式更新。六、协程测试runTest 与虚拟时间规则指出所有协程相关测试都应使用runTestTest fun parallel operations complete() runTest { val repo FakeRepository() val result loadDashboard(repo) advanceUntilIdle() assertNotNull(result.items) assertNotNull(result.stats) }runTest的核心能力是自动推进虚拟时间并提供TestScope原文「仮想時間を自動的に進め、TestScopeを提供する」。这意味着测试中的delay(...)不再真实等待而是被虚拟时钟瞬间跳过测试速度大幅提升且确定性极高。advanceUntilIdle()用于把虚拟时钟推进到所有待执行协程包括async并行任务全部完成——这正是上面loadDashboard这类「并行加载仪表盘」场景的关键。需要补充的一点runTest默认使用StandardTestDispatcher被测 ViewModel 内部viewModelScope.launch派发的任务同样在该测试调度器上排队因此测试是确定的若测试中有自定义的Dispatchers注入点可在测试中替换为UnconfinedTestDispatcher或标准测试调度器以保证时序可控。kotlinx-coroutines-test提供的TestDispatcher体系StandardTestDispatcher/UnconfinedTestDispatcher/TestScope是这条规则的底层支撑。七、网络层测试Ktor MockEngine针对基于 Ktor 的 HTTP 客户端规则推荐使用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() } }要点拆解MockEngine接收一个以HttpRequestData为入参、返回HttpResponseData的 lambda通过request.url.encodedPath做基于路径的路由命中路径返回 JSON 序列化内容并显式设置Content-Type: application/json响应头——这保证了ContentNegotiation的 JSON 反序列化链路在测试中真实生效未命中路径返回404可用来断言客户端对错误状态码的处理如抛出自定义异常或降级逻辑。通过为同一个HttpClient注入不同的HttpClientEngine即可在「测试环境用MockEngine、生产环境用真实引擎如OkHttp/CIO/Darwin」之间无缝切换无需改动上层 Repository 的调用代码。八、数据库层测试Room 与 SQLDelight规则给出两类数据库的测试策略Room使用Room.inMemoryDatabaseBuilder()构建内存数据库测试结束后自动销毁无需清理文件SQLDelightJVM 测试使用JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY)内存驱动。SQLDelight 示例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) }这里体现了 SQLDelight 的典型流程先用Database.Schema.create(driver)在内存驱动上建表再创建Database实例随后直接调用编译期生成的类型安全查询itemQueries。由于驱动是纯内存的每个测试方法天然隔离配合runTest即可稳定执行。规则没有展开的是若测试涉及异步 Flow 式查询Flow返回值同样可用 Turbine 对数据库变更流做断言而 Android 平台上的插桩测试androidInstrumentedTest则可使用Room.inMemoryDatabaseBuilder(context, ...)验证真实 SQLite 行为。九、测试命名反引号内的行为描述规则要求使用反引号包裹的描述性命名把测试方法名写成一句可读的英文行为描述Test fun search with empty query returns all items() runTest { } Test fun delete item emits updated list without deleted item() runTest { }这种命名与 rules/common/testing.md 中「使用能解释被测行为的描述性名称」一脉相承公共规则中的示例为returns empty array when no markets match query等。其好处是测试失败时失败信息本身就是一句可读的需求陈述如「search with empty query returns all items」无需跳回代码解读同时测试名可反推被测行为天然充当可执行的需求文档。十、KMP 测试目录组织规则给出多平台项目的标准测试目录结构src/ ├── commonTest/kotlin/ # 共享测试ViewModel、UseCase、Repository ├── androidUnitTest/kotlin/ # Android 单元测试JUnit ├── androidInstrumentedTest/kotlin/ # 插桩测试Room、UI └── iosTest/kotlin/ # iOS 专属测试分层逻辑与 Kotlin Multiplatform 的src源集结构一一对应commonTest放置与平台无关的共享逻辑测试——ViewModel、UseCase、Repository 的测试都应在此这也呼应了「每个功能至少覆盖 ViewModel UseCase」的最低要求androidUnitTest依赖 Android 框架JVM 可模拟部分的单元测试使用 JUnit 4/5androidInstrumentedTest需要真机/模拟器运行、访问 Room 数据库或 UI 的插桩测试iosTestiOS 平台专属逻辑如expect/actual的 iOS 实现测试。十一、与 TDD 工作流和 Agent 的协作Kotlin 测试规则并非孤立存在它嵌在 ECC 更宏观的开发流程中TDD 流程公共规则强制「先写测试RED→ 确认失败 → 最小实现GREEN→ 重构IMPROVE→ 验证 80% 覆盖率」。Kotlin 专项规则提供的就是 RED 阶段「怎么写」的具体武器——Turbine 写 ViewModel 测试、Fake 替身、runTest虚拟时间、MockEngine打桩网络层。tdd-guideAgent公共规则与 agents/tdd-guide.md 均指明开发新功能时应主动激活tdd-guideAgent。该 Agent 的角色定义是「Test-Driven Development specialist enforcing write-tests-first methodology」负责强制「先测试后代码」、引导 Red-Green-Refactor 循环、确保 80% 覆盖率并编写单元/集成/E2E 测试套件——Kotlin 专项规则是它在 Kotlin/Android/KMP 项目中的具体检查清单。十二、常见反模式与陷阱结合 skills/kotlin-coroutines-flows/SKILL.md 的「Anti-Patterns to Avoid」清单与本文规则相关的常见陷阱包括使用GlobalScope协程泄漏、无结构化取消测试中也无法由TestScope统一控制应一律使用viewModelScope等生命周期作用域在init {}中无作用域收集 Flow应改为viewModelScope.launch对可变集合使用MutableStateFlow状态更新必须走不可变副本如_state.update { it.copy(list it.list newItem) }否则状态流无法正确触发重组与断言捕获CancellationException应让其继续传播以保证取消语义测试中吞掉取消异常会导致runTest超时或悬挂在Composable中不借助remember创建 Flow每次重组都重建流导致 Turbine 断言到的流实例与 UI 实际消费的不一致。十三、小结ECC 的 Kotlin 测试规则提供了一条完整的实践路径以kotlin.test JUnit 打底用 Turbine 精确断言 Flow/StateFlow 的发射时序用runTest掌控虚拟时间用 Fake 替身替代 Mock 框架用 KtorMockEngine隔离网络层用内存驱动测试 Room/SQLDelight 数据库再以行为化命名与 KMP 源集目录组织保证可读性与可维护性。它与公共 80% 覆盖率基线、TDD 强制流程及tdd-guideAgent 共同构成「规范可查、示例可跑、工具可循」的 Kotlin 质量保障闭环。需要深入时可继续阅读本仓库中的相关文档rules/common/testing.md公共测试基线、rules/kotlin/patterns.mdViewModel/UseCase/Repository 模式本文示例的架构前提、skills/kotlin-coroutines-flows/SKILL.md协程与 Flow 测试进阶与反模式、agents/tdd-guide.mdTDD 强制流程 Agent。【免费下载链接】ECCThe agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Cursor and beyond.项目地址: https://gitcode.com/GitHub_Trending/ev/ECC创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考