29、WiFi测试与模拟:使用Android Emulator模拟WiFi、使用adb模拟WiFi状态、单元测试与UI测试
说实话,很多开发者在做WiFi功能时,最容易忽略的就是测试环节。大家总觉得「连上路由器能上网就行了」,结果一到真机适配就翻车。我早期做WiFi项目时也吃过这个亏——在模拟器上跑得好好的,到了某款国产手机上直接闪退。后来我才意识到,WiFi测试不是可有可无的步骤,而是保证稳定性的底线。
这一章,我就带你系统梳理一下WiFi测试的几种手段。从模拟器模拟,到adb命令控制,再到单元测试和UI测试,咱们一步步来。
29.1 使用Android Emulator模拟WiFi
Android Emulator其实内置了WiFi模拟能力。很多人不知道,它默认就有一个虚拟的WiFi网络。你打开模拟器的设置,进入WiFi页面,就能看到已经连上了一个叫「AndroidWifi」的网络。
但光有默认网络还不够。有时候我们需要测试不同的WiFi环境,比如弱信号、不同加密方式、甚至多个可用网络。这时候就要手动配置了。
核心操作步骤:
- 打开模拟器的「Extended Controls」(扩展控制面板)
- 选择「Telephony」或「Cellular」旁边的「WiFi」选项卡
- 在这里可以添加虚拟WiFi网络,设置SSID、BSSID、加密类型(WPA2/WPA3/开放)
- 还可以调整信号强度,从-30 dBm(极强)到-90 dBm(极弱)
我个人习惯在测试WiFi扫描逻辑时,至少添加3-4个虚拟网络。一个信号强的,一个信号弱的,一个加密的,一个开放的。这样能覆盖大部分真实场景。
小技巧:模拟器的WiFi模拟只支持API 25以上的系统镜像。如果你用的是旧版镜像,可能找不到这个功能。建议用API 28以上的镜像。
29.2 使用adb模拟WiFi状态
模拟器图形界面虽然方便,但自动化测试时就不太够用了。这时候adb命令才是王道。
adb控制WiFi的核心命令是 adb shell cmd wifi。这个命令在Android 10及以上版本中功能非常强大。
# 查看当前WiFi状态
adb shell dumpsys wifi | grep "Wi-Fi"
# 启用/禁用WiFi
adb shell svc wifi enable
adb shell svc wifi disable
# 模拟连接到一个网络(需要先知道网络ID)
adb shell cmd wifi connect-network "MyTestWiFi" wpa2 "password123"
# 模拟断开当前连接
adb shell cmd wifi disconnect
# 模拟信号强度变化(需要root或使用模拟器)
adb shell cmd wifi set-wifi-enabled enabled
我曾经在做一个WiFi切换功能时,需要反复测试从弱信号网络切换到强信号网络的逻辑。手动操作太慢了,我就写了个adb脚本,每隔5秒切换一次信号强度,配合自动化测试跑了一整晚。第二天一看日志,抓到了三个边界情况下的崩溃。
注意:adb的 cmd wifi 命令在非root的真机上权限受限。模拟器上基本都能用,真机测试建议用 svc wifi 系列命令。另外,dumpsys wifi 的输出内容非常多,建议用grep过滤关键信息。
29.3 单元测试:验证WiFi逻辑的正确性
单元测试这块,说白了就是保证你的WiFi相关代码在脱离真机的情况下也能跑通。我见过太多人把网络请求、WiFi扫描这些逻辑直接写在Activity里,结果想测都没法测。
正确的做法是把WiFi操作封装到Repository或ViewModel中,然后用Mockito模拟WiFiManager的行为。
// 一个典型的WiFi Repository单元测试
@RunWith(MockitoJUnitRunner.class)
public class WiFiRepositoryTest {
@Mock
private WifiManager mockWifiManager;
@Mock
private ConnectivityManager mockConnectivityManager;
private WiFiRepository repository;
@Before
public void setUp() {
repository = new WiFiRepository(mockWifiManager, mockConnectivityManager);
}
@Test
public void testIsWiFiEnabled_returnsTrue() {
// 模拟WiFi已开启
when(mockWifiManager.isWifiEnabled()).thenReturn(true);
boolean result = repository.isWiFiEnabled();
assertTrue(result);
verify(mockWifiManager).isWifiEnabled();
}
@Test
public void testScanResults_returnsList() {
List<ScanResult> mockResults = new ArrayList<>();
ScanResult result = new ScanResult();
result.SSID = "TestNetwork";
result.level = -50;
mockResults.add(result);
when(mockWifiManager.getScanResults()).thenReturn(mockResults);
List<ScanResult> results = repository.getScanResults();
assertEquals(1, results.size());
assertEquals("TestNetwork", results.get(0).SSID);
}
}
为什么我要强调单元测试?因为WiFi相关的系统服务在模拟器上行为可能和真机不一致。比如模拟器上的 getScanResults() 返回的结果列表,和真机上的排序规则可能不同。单元测试能帮你提前发现这些差异。
我的经验:写WiFi单元测试时,重点关注三个点:
- WiFi开关状态的正确传递
- 扫描结果列表的排序和过滤逻辑
- 网络连接状态变化的回调处理
这三个点最容易出bug,也最适合用单元测试覆盖。
29.4 UI测试:用Espresso验证WiFi界面
单元测试保证了逻辑正确,但用户界面好不好用还得靠UI测试。Android官方推荐的UI测试框架是Espresso,配合AndroidX Test使用。
UI测试WiFi功能时,我一般会测试这几个场景:
- WiFi开关打开后,列表是否显示扫描结果
- 点击一个网络后,是否弹出密码输入框
- 输入错误密码时,是否显示错误提示
- WiFi关闭时,界面是否显示「未连接」状态
@RunWith(AndroidJUnit4.class)
public class WiFiListFragmentTest {
@Rule
public ActivityTestRule<MainActivity> activityRule =
new ActivityTestRule<>(MainActivity.class);
@Test
public void testToggleWiFi_switchChangesState() {
// 找到WiFi开关
onView(withId(R.id.wifi_switch))
.perform(click());
// 验证开关状态已改变
onView(withId(R.id.wifi_switch))
.check(matches(isChecked()));
// 验证列表开始显示扫描结果
onView(withId(R.id.scan_results_list))
.check(matches(isDisplayed()));
}
@Test
public void testClickNetwork_showsPasswordDialog() {
// 先确保WiFi已开启
onView(withId(R.id.wifi_switch))
.perform(click());
// 点击列表中的第一个网络
onView(withId(R.id.scan_results_list))
.perform(RecyclerViewActions
.actionOnItemAtPosition(0, click()));
// 验证密码对话框出现
onView(withText("输入密码"))
.check(matches(isDisplayed()));
}
}
写UI测试时有个坑——模拟器上的WiFi扫描结果可能为空。我建议在测试前先用adb命令添加一个虚拟网络,或者用MockWebServer模拟网络数据。不然测试跑起来,列表是空的,点击操作就会失败。
避坑指南:我曾经在CI上跑UI测试,发现WiFi列表总是空的。查了半天,原来是模拟器启动后WiFi模块还没完全初始化。解决方案是在测试的 @Before 方法里加一个 SystemClock.sleep(2000),等WiFi服务稳定后再操作。
29.5 测试策略总结
说了这么多,其实就一个核心思想:不同阶段用不同的测试手段。
| 测试类型 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 模拟器WiFi模拟 | 功能验证、UI调试 | 可视化、操作直观 | 无法自动化 |
| adb命令模拟 | 自动化测试、压力测试 | 可脚本化、快速 | 需要熟悉命令 |
| 单元测试 | 逻辑验证、边界条件 | 快速、可靠、可重复 | 无法测试UI |
| UI测试 | 界面交互、用户体验 | 覆盖真实操作流程 | 运行慢、环境依赖 |
我个人习惯的开发流程是这样的:写代码时先用模拟器手动验证一遍,然后写单元测试覆盖核心逻辑,最后用adb脚本配合UI测试做回归。这样一套下来,WiFi功能基本不会出大问题。
嗯,测试这块就聊到这儿。记住一点:WiFi测试不是「有就行」,而是要「覆盖全」。你永远不知道用户会在什么样的网络环境下使用你的App——可能是信号满格的办公室,也可能是只有一格信号的电梯里。测试做得越细,线上翻车的概率就越低。