第二十六章:测试与 Mock:UnitTest 框架、MockWifiService、集成测试
说实话,写 Wi-Fi 服务最怕什么?不是逻辑复杂,而是改了一行代码,不知道把哪里搞崩了。Wi-Fi 这东西涉及硬件、驱动、框架、应用层,牵一发动全身。我早年吃过这个亏——改了个扫描逻辑,结果某些机型上连不上隐藏网络,查了两天才发现是单元测试没覆盖到。
所以这一章,咱们聊聊怎么给 Wi-Fi Service 做测试。说白了,就是给你的代码上保险。
为什么 Wi-Fi Service 测试这么难搞?
你想想看,Wi-Fi Service 依赖的东西太多了:
- 硬件状态(Wi-Fi 芯片开没开)
- 系统服务(WifiManager、ConnectivityManager)
- 异步回调(扫描结果、连接状态)
- 权限校验
这些在单元测试里都不好模拟。我见过不少项目,Wi-Fi 模块的测试覆盖率不到 10%。不是不想写,是真的不好写。
那怎么办?答案是:Mock。
UnitTest 框架选型
Android 这边,主流的单元测试框架就那几个:
| 框架 | 适用场景 | 我的评价 |
|---|---|---|
| JUnit 4/5 | 基础单元测试 | 必选,没啥好说的 |
| Mockito | Mock 依赖对象 | 最常用,我基本每个项目都用 |
| PowerMock | Mock 静态方法、final 类 | 能不用就不用,有坑 |
| Robolectric | 模拟 Android 框架 | 集成测试利器 |
我个人习惯是:纯逻辑用 JUnit + Mockito,涉及 Android 框架用 Robolectric。
MockWifiService:把硬件“骗”过去
Wi-Fi Service 里最难测的是什么?是那些跟硬件交互的方法。比如 startScan(),你没法在测试里真的去扫 Wi-Fi 吧?
所以我们要造一个假的 Wi-Fi 服务——MockWifiService。
来看个例子。假设我们有这样一个真实的服务:
public class WifiServiceImpl {
private final WifiNative mWifiNative;
public boolean startScan() {
if (!mWifiNative.isDriverLoaded()) {
return false;
}
return mWifiNative.scan();
}
}
测试的时候,我们不想真的加载驱动。那就 Mock 掉 WifiNative:
@Test
public void testStartScan_driverNotLoaded_returnsFalse() {
// 准备
WifiNative mockNative = mock(WifiNative.class);
when(mockNative.isDriverLoaded()).thenReturn(false);
WifiServiceImpl service = new WifiServiceImpl(mockNative);
// 执行
boolean result = service.startScan();
// 验证
assertFalse(result);
verify(mockNative, never()).scan();
}
这里的关键是:把依赖“注入”进去,而不是在内部 new 出来。这样测试时就能替换成 Mock 对象。
集成测试:把整个流程跑通
单元测试只能测单个方法。但 Wi-Fi 的流程往往是跨模块的——比如“用户点击连接 → 框架层发起连接 → 驱动层配置网络 → 返回结果”。
这种场景,就得靠集成测试了。
我一般用 Robolectric 来做。它能模拟 Android 的运行环境,让你在 JVM 上跑测试,不用真机。
@RunWith(RobolectricTestRunner.class)
public class WifiServiceIntegrationTest {
@Test
public void testConnectFlow() {
// 模拟系统服务
ShadowApplication shadowApp = Shadows.shadowOf(ApplicationProvider.getApplicationContext());
shadowApp.setSystemService(Context.WIFI_SERVICE, new MockWifiManager());
// 执行连接
WifiManager wifiManager = (WifiManager) shadowApp.getApplicationContext()
.getSystemService(Context.WIFI_SERVICE);
boolean connected = wifiManager.connect(new WifiConfiguration(), null);
// 验证状态
assertTrue(connected);
// 检查是否调用了底层的 connectNative()
}
}
嗯,这里要注意:Robolectric 的 Shadow 对象不是万能的。有些底层操作它模拟不了,比如真正的驱动加载。这时候就得靠 Mock 来补位。
避坑指南
- Mock 了不该 Mock 的东西——比如把 WifiManager 整个 Mock 掉,结果测试通过了,但上线后发现 WifiManager 的某些行为跟 Mock 的不一样。教训是:只 Mock 你控制不了的依赖,不要 Mock 你要测的类本身。
- 异步回调没处理好——Wi-Fi 的很多操作是异步的(扫描、连接)。测试里要用
ArgumentCaptor捕获回调,或者用CountDownLatch等待异步完成。 - 忘了重置 Mock 状态——多个测试用例共用 Mock 对象时,记得在每个测试前重置,否则上一个测试的调用记录会影响下一个。
测试金字塔在 Wi-Fi 模块的应用
我一般这样分配测试比例:
- 单元测试(70%):测每个 Manager、Service 的独立逻辑
- 集成测试(20%):测跨模块的流程,比如扫描→结果回调→UI 更新
- 端到端测试(10%):在真机上跑,测完整的用户操作路径
这个比例不是拍脑袋定的。我试过把单元测试压到 50%,结果集成测试跑起来一堆问题,定位还特别慢。后来老老实实把单元测试补上去,反而整体效率更高了。
SVG:Wi-Fi Service 测试架构图
下面这张图,是我做 Wi-Fi 测试时的分层思路:
从下往上看,测试成本越来越高,但覆盖的场景也越来越真实。我建议你从单元测试做起,先把逻辑层测稳了,再往上走。
总结一下
Wi-Fi Service 的测试,说白了就是三件事:
- 用 Mock 把硬件和系统服务“架空”,让测试只关注你的业务逻辑
- 用 Robolectric 模拟 Android 环境,跑通跨模块的流程
- 在真机上做最后的验证,确保跟硬件交互没问题
我个人的经验是:测试代码写得好不好,直接决定了你改代码的时候敢不敢重构。如果你每次改 Wi-Fi 代码都提心吊胆,那多半是测试没到位。
嗯,这一章就到这儿。代码写完了,别忘了跑一遍测试再提交。