第七篇:进阶篇 —— 工程化与质量保障 第14章 自动化测试:构筑代码质量的防火墙
第七篇:进阶篇 —— 工程化与质量保障第14章 自动化测试:构筑代码质量的防火墙在第十三章,我们通过 Profiler 优化了 App 的性能。但如果我们改了一行代码,修复了一个 Bug,却不小心引入了三个新 Bug 怎么办?靠人工点点点测试,在 2026 年是低效且不可靠的。自动化测试是专业开发者的护城河。它是一张安全网,让你敢于重构代码、升级依赖、添加新功能,而无需担心把 App 搞崩。本章将带你构建三层测试体系:单元测试(Unit Test)、集成测试(Integration Test)和UI 测试(UI Test)。我们将使用 2026 年最新的测试栈:JUnit 5 + MockK + Turbine + Compose UI Test。我们将深入测试的哲学、Mock 的底层原理、以及如何测试 Kotlin 协程和 Flow。14.1 测试金字塔:你应该写哪些测试?(深度解析)并不是测试越多越好,而是要分层。测试类型占比速度成本关注点2026年工具栈UI 测试10%慢高模拟用户操作,验证界面流程(如:点击登录按钮是否跳转到主页)。Compose UI Test集成测试20%中中验证模块协作(如:ViewModel 调用 Repository 是否能拿到数据)。Hilt + Robolectric单元测试70%极快低验证最小功能单元(如:一个工具函数、一个 ViewModel 的状态计算)。JUnit 5 + MockK + Turbine核心原则:测试应该是快速的。如果你跑一次测试需要 5 分钟,没人会跑。14.1.1 为什么是 70/20/10?单元测试(70%):最便宜,最快。它们不依赖 Android 框架,直接在 JVM 上运行。你应该为每一个业务逻辑类编写单元测试。集成测试(20%):验证组件之间的交互。例如,ViewModel 是否正确调用了 Repository,数据库是否正确保存了数据。UI 测试(10%):最昂贵,最慢。它们运行在真机或模拟器上,模拟真实用户操作。只为核心业务流程编写 UI 测试。14.2 单元测试:JUnit 5 与 MockK(深度解析)单元测试是针对纯 Kotlin 代码的测试,不依赖 Android 设备,直接在 JVM 上运行。14.2.1 配置测试环境(深度解析)// build.gradle.kts (App)dependencies{// JUnit 5 (Jupiter)testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")testRuntimeOnly("org.junit.platform:junit-platform-launcher")// MockK (Kotlin 专用 Mock 框架,替代 Mockito)testImplementation("io.mockk:mockk:1.13.9")// Turbine (测试 Flow 的神器)testImplementation("app.cash.turbine:turbine:1.0.0")// 测试协程testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.0")}14.2.2 JUnit 5 注解详解(深度解析)注解作用示例@Test标记一个测试方法@Test fun addition_isCorrect()@BeforeEach每个测试方法前执行@BeforeEach fun setup()@AfterEach每个测试方法后执行@AfterEach fun tearDown()@BeforeAll所有测试方法前执行一次@BeforeAll companion object { ... }@AfterAll所有测试方法后执行一次@AfterAll companion object { ... }@DisplayName测试显示名称@DisplayName("加法测试")@Nested嵌套测试类@Nested inner class AdditionTests@Timeout超时测试@Timeout(1, unit = TimeUnit.SECONDS)14.2.3 测试一个简单的工具类(深度实战)假设我们有一个StringUtils.kt:// utils/StringUtils.ktobjectStringUtils{funisValidEmail(email:String):Boolean{returnemail.contains("@")email.contains(".")}funformatPrice(price:Double):String{return"¥${String.format("%.2f",price)}"}}编写测试:// test/java/com/example/myfirstapp/utils/StringUtilsTest.ktimportorg.junit.jupiter.api.Assertions.assertFalseimportorg.junit.jupiter.api.Assertions.assertTrueimportorg.junit.jupiter.api.DisplayNameimportorg.junit.jupiter.api.TestclassStringUtilsTest{@Test@DisplayName("Valid email should return true")fun`isValidEmail returns true for valid email`(){// Givenvalemail="test@example.com"// Whenvalresult=StringUtils.isValidEmail(email)// ThenassertTrue(result)