23、蓝牙自动化测试:Android Instrumentation Test、BLE模拟器与自动化脚本
说实话,蓝牙开发最让人头疼的不是写代码,而是测试。手动测试?一次两次还行,反复几十次你肯定会崩溃。我当年做一个智能门锁项目,每天要重复配对、通信、断连几十遍,手指都快按出腱鞘炎了。后来我下定决心,必须搞自动化。
这一章,我就把压箱底的自动化测试方案拿出来。咱们分三块讲:Android 自带的 Instrumentation Test、Nordic 的 BLE 模拟器、还有用 Python+adb 写自动化脚本。这三板斧用好了,测试效率能翻好几倍。
23.1 Android Instrumentation Test:让 App 自己测自己
Instrumentation Test 是 Android 官方提供的测试框架。说白了,就是写一段代码,让 App 自动执行一系列操作,然后检查结果对不对。
为什么要用它测蓝牙?因为蓝牙操作涉及系统服务、权限、状态机,纯单元测试很难覆盖全。Instrumentation Test 跑在真机上,能真实调用蓝牙 API。
23.1.1 环境配置
在 app/build.gradle 里加上依赖:
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'
嗯,这里要注意,UiAutomator 在 Android 10 以上需要额外权限,记得在测试代码里加 @GrantPermission 注解。
23.1.2 写一个蓝牙扫描测试
我习惯把测试用例按功能分组。先来个扫描测试:
@RunWith(AndroidJUnit4.class)
public class BleScanTest {
private BluetoothLeScanner scanner;
private CountDownLatch latch;
@Before
public void setUp() {
BluetoothManager manager = (BluetoothManager)
InstrumentationRegistry.getInstrumentation().getTargetContext()
.getSystemService(Context.BLUETOOTH_SERVICE);
BluetoothAdapter adapter = manager.getAdapter();
scanner = adapter.getBluetoothLeScanner();
}
@Test
public void testScanForDevices() throws InterruptedException {
latch = new CountDownLatch(1);
ScanCallback callback = new ScanCallback() {
@Override
public void onScanResult(int callbackType, ScanResult result) {
// 只要扫描到设备就算通过
assertNotNull(result.getDevice());
latch.countDown();
}
};
scanner.startScan(callback);
boolean scanned = latch.await(10, TimeUnit.SECONDS);
assertTrue("10秒内未扫描到设备", scanned);
scanner.stopScan(callback);
}
}
这段代码的核心是 CountDownLatch。为什么用它?因为蓝牙扫描是异步的,你不能直接 assert,得等回调回来。我刚开始写的时候没用这个,结果测试永远通过——因为 assert 在回调之前就执行完了,根本没检查到任何东西。
23.1.3 配对与通信测试
配对测试稍微复杂点。你需要先让设备进入可发现模式,然后调用 createBond:
@Test
public void testPairing() throws Exception {
BluetoothDevice device = ... // 从扫描结果获取
Method method = device.getClass().getMethod("createBond");
boolean result = (boolean) method.invoke(device);
assertTrue("配对失败", result);
// 等待配对完成
Thread.sleep(3000);
assertEquals(BluetoothDevice.BOND_BONDED, device.getBondState());
}
注意:createBond 是隐藏 API,在 Android 10+ 上可能被限制。我建议用 BroadcastReceiver 监听配对状态变化,而不是直接 sleep。
23.2 BLE 模拟器:Nordic nRF Connect SDK
真机测试有个痛点——你不可能每次都有完整的硬件设备。尤其是早期开发阶段,硬件可能还没出来。这时候就需要 BLE 模拟器了。
Nordic 的 nRF Connect SDK 提供了 PC 端的 BLE 模拟器。它可以在你的电脑上模拟一个完整的 BLE 外设,包括 Service、Characteristic、Notification。
23.2.1 搭建模拟外设
用 nRF Connect 桌面版,新建一个模拟设备:
- 打开 nRF Connect,点击「Create Configuration」
- 添加 Service:比如 0xFFE0(自定义服务)
- 添加 Characteristic:0xFFE1,属性设为 Read/Write/Notify
- 启动模拟器,设备就开始广播了
你想想看,这比拿真机调试方便多了。我在做心率监测 App 的时候,就是先用模拟器生成各种心率数据,把 UI 和逻辑调通,最后才连真机。
23.2.2 模拟异常场景
模拟器还能干一件事——模拟异常。比如:
- 断开连接:模拟信号中断
- 延迟响应:模拟网络延迟
- 错误数据:模拟设备返回乱码
这些场景在真机上很难复现,但用模拟器一键就能搞定。我曾经遇到过一个 bug,设备偶尔会返回长度不对的数据包,用真机测了一周没复现,换模拟器半小时就找到了。
23.3 Python + adb:自动化脚本三板斧
Instrumentation Test 适合测 App 内部逻辑,但有些场景它搞不定。比如:
- 系统蓝牙开关控制
- 多设备配对流程
- 长时间稳定性测试
这时候就该 Python + adb 上场了。说白了,就是用 Python 调用 adb 命令,模拟用户操作。
23.3.1 控制蓝牙开关
import subprocess
def toggle_bluetooth(on=True):
state = "enable" if on else "disable"
cmd = f"adb shell svc bluetooth {state}"
subprocess.run(cmd, shell=True)
def is_bluetooth_on():
result = subprocess.run(
"adb shell dumpsys bluetooth_manager | grep 'state:'",
shell=True, capture_output=True, text=True
)
return "STATE_ON" in result.stdout
这里用 svc bluetooth 而不是 settings 命令,因为它不需要 root 权限。我踩过这个坑,一开始用 settings 命令,结果在部分手机上没效果。
23.3.2 自动化配对脚本
配对流程通常涉及点击系统弹窗。用 adb 模拟点击:
def pair_device(address):
# 1. 打开蓝牙设置
subprocess.run("adb shell am start -a android.settings.BLUETOOTH_SETTINGS", shell=True)
time.sleep(2)
# 2. 点击扫描到的设备
subprocess.run(f"adb shell input tap 500 800", shell=True)
time.sleep(3)
# 3. 确认配对弹窗
subprocess.run("adb shell input tap 540 1200", shell=True)
time.sleep(5)
# 4. 检查配对状态
result = subprocess.run(
f"adb shell dumpsys bluetooth_manager | grep {address}",
shell=True, capture_output=True, text=True
)
return "BOND_BONDED" in result.stdout
小技巧:不同手机的弹窗位置不一样。我一般先用 adb shell uiautomator dump 导出当前界面布局,然后解析 XML 找到按钮坐标。这样脚本就能适配不同分辨率。
23.3.3 通信压力测试
长时间通信测试,我写过一个循环脚本:
def stress_test(iterations=100):
for i in range(iterations):
# 发送数据
subprocess.run("adb shell am broadcast -a com.example.SEND_DATA --es data 'hello'", shell=True)
time.sleep(0.5)
# 检查日志
log = subprocess.run(
"adb logcat -d -s BleService:I | tail -5",
shell=True, capture_output=True, text=True
)
if "ERROR" in log.stdout:
print(f"第{i+1}次通信异常")
break
print(f"第{i+1}次通信正常")
time.sleep(1)
这个脚本我跑过一整夜,第二天早上看结果,能发现很多偶发问题。比如内存泄漏、连接超时、数据丢包,这些在手动测试时很难察觉。
23.4 知识体系总览
下面这张图,我把本章的核心逻辑串起来了:
23.5 实战建议
最后,我根据多年经验,给你几条实在的建议:
- 先模拟后真机:用 nRF Connect 模拟器把逻辑调通,再上真机。能省一半时间。
- 日志要打全:自动化测试最怕失败后不知道原因。每个关键步骤都打日志,包括时间戳、状态、数据内容。
- 异常处理要到位:蓝牙操作经常超时,脚本里一定要加 try-except 和重试机制。我见过太多脚本跑一半就挂在那里的情况。
- CI/CD 集成:把自动化测试集成到 Jenkins 或 GitHub Actions 里,每次提交代码自动跑一遍。这样能尽早发现问题。
核心要点:Instrumentation Test 测 App 逻辑,BLE 模拟器测设备交互,Python+adb 测系统流程。三者结合,才能覆盖蓝牙开发的全部测试场景。
好了,这一章的内容就到这里。自动化测试是个持续投入的过程,一开始写脚本可能觉得麻烦,但一旦跑起来,你会发现它帮你省下的时间远超预期。