为什么测试是给重构买保险

没测试时改代码像走雷区:怕动一处坏三处。有了测试,每次改动跑完用例就能知道有没有把旧功能弄坏。测试的最大回报不在写的那一刻,而在三个月后你敢不敢大改。

测试不是为了证明代码能跑,而是为了让未来的修改有安全网。

这篇从单元测试到 UI 测试,讲怎么在安卓里把这件事落地。

测试金字塔

测试分三层,数量从底到顶递减。底层是大量快速的单元测试,测纯逻辑;中层是集成测试,测模块协作;顶层是少量慢速的 UI 测试,测关键用户路径。全堆 UI 测试会又慢又脆,全堆单元测试又覆盖不到真实流程。

三层怎么配
  • 单元测试最多最快,测 ViewModel/工具类。
  • 集成测试测数据库、仓库层。
  • UI 测试最少最关键,测主流程。

第一个 JUnit 单元测试

单元测试放在 test 目录,跑在本地 JVM 上,不依赖安卓设备。给定输入、执行方法、断言输出。

import org.junit.Assert.assertEquals
import org.junit.Test

class CalcTest {
  @Test fun `正数相加正确`() {
    assertEquals(3, add(1, 2))
  }
}

方法名用反引号写中文描述,失败时一眼看懂哪个场景出了问题。

用 Mock 隔离依赖

被测逻辑如果真的去发网络请求或读数据库,测试又慢又不稳定。用 Mock 把依赖替换成假对象,只验证"调用了正确的方法",不关心真实实现。

@Test fun `登录成功会回调用户`() = runTest {
  whenever(repo.login("a", "b")).thenReturn(User("a"))
  vm.login()
  assertEquals("a", vm.ui.value.user?.name)
}

仓库被打桩,测试只关注 ViewModel 的状态流转,毫秒级跑完。

ViewModel 与协程测试

测带协程的 ViewModel 要用 runTest,它把虚拟调度器跑快,不用真等延时。把 viewModelScope 换成测试调度器,异步逻辑就变成同步可断言的。

安卓测试示意:中央一面发光盾牌上有对勾符号,左侧一支装蓝色液体的试管,两侧是波形曲线
质量校验:盾牌、对勾与波形,象征测试为代码兜底。

核心断言状态流:加载中、成功、失败三种分支都要有用例覆盖。