18、测试与调试:单元测试、UI测试(Espresso)、模拟器调试、实车测试注意事项

说实话,做车载开发最怕什么?不是需求改来改去,而是你写了一大堆代码,结果上车一跑,直接黑屏或者卡死。那种感觉,就像你精心准备了一桌菜,结果客人一筷子下去,盘子碎了。

所以这一章,咱们聊聊测试与调试。我个人的习惯是:能早测就早测,能自动测就别手动点。尤其是在Android Automotive OS这种对稳定性要求极高的场景下,测试不是可选项,而是保命符。

Android Automotive OS 测试与调试知识体系 测试与调试 单元测试 UI测试 (Espresso) 模拟器调试 实车测试注意事项 JUnit + Mockito CarService测试 onView + onData IdlingResource AAOS模拟器镜像 ADB + logcat 安全 & 稳定性 实车日志采集

18.1 单元测试:别让Bug活到集成阶段

单元测试,说白了就是给每个函数、每个类单独做体检。你想想看,如果每个零件出厂前都检查过,组装起来出问题的概率就小很多。

在Android Automotive OS里,我特别强调对CarService相关代码做单元测试。为什么?因为CarService是车载系统的核心,它管着空调、车窗、座椅这些硬件。一旦这里出问题,用户直接体验到的就是「这车机不行」。

核心要点:

  • 使用 JUnit 4/5 作为测试框架
  • Mockito 模拟硬件依赖(比如VehicleHal)
  • 重点测试 CarPropertyManagerCarSensorManager 的回调逻辑

举个例子,假设你要测试一个空调温度调节的逻辑:

@RunWith(MockitoJUnitRunner.class)
public class HvacControllerTest {
    
    @Mock
    private CarPropertyManager mockPropertyManager;
    
    @InjectMocks
    private HvacController hvacController;
    
    @Test
    public void setTemperature_shouldCallCarPropertyManager() {
        // 准备
        float targetTemp = 24.5f;
        
        // 执行
        hvacController.setTemperature(targetTemp);
        
        // 验证
        verify(mockPropertyManager).setProperty(
            eq(VehiclePropertyIds.HVAC_TEMPERATURE_SET),
            eq(AreaId.DRIVER),
            eq(targetTemp)
        );
    }
}

这段代码看着简单,但我在项目中遇到过一个问题:AreaId传错了。当时测试没覆盖这个参数,结果上车后主驾调温度,副驾跟着变。你说尴尬不尴尬?

我的小技巧: 写单元测试时,别只测「正常路径」。多想想「如果硬件返回null怎么办?」、「如果回调超时呢?」。这些边界情况,往往是线上事故的根源。

18.2 UI测试(Espresso):让机器替你点点点

UI测试,说白了就是让程序模拟用户操作。你想想,一个车载应用可能有几十个界面,每次改完代码都手动点一遍?那不得累死。

Espresso是Google官方的UI测试框架,用起来很顺手。但在AAOS里,有几个坑你得注意。

18.2.1 基本用法

@RunWith(AndroidJUnit4.class)
public class MainScreenTest {
    
    @Rule
    public ActivityTestRule<MainActivity> activityRule = 
        new ActivityTestRule<>(MainActivity.class);
    
    @Test
    public void clickMusicButton_shouldOpenMusicApp() {
        // 找到音乐按钮并点击
        onView(withId(R.id.btn_music))
            .perform(click());
        
        // 验证音乐应用是否启动
        onView(withText("音乐"))
            .check(matches(isDisplayed()));
    }
}

这段代码看着没问题吧?但我在实车上跑的时候,发现点击后界面没反应。后来排查发现,车载系统里很多操作需要等待硬件响应,而Espresso默认的超时时间太短了。

注意: AAOS中很多UI操作涉及硬件交互(比如调节音量、切换音源),响应时间可能比普通手机应用长。建议使用 IdlingResource 来等待异步操作完成,或者适当增加超时时间。

18.2.2 IdlingResource 的使用

public class HvacIdlingResource implements IdlingResource {
    
    private volatile boolean isIdle = false;
    private ResourceCallback callback;
    
    @Override
    public String getName() {
        return "HvacIdlingResource";
    }
    
    @Override
    public boolean isIdleNow() {
        return isIdle;
    }
    
    @Override
    public void registerIdleTransitionCallback(ResourceCallback callback) {
        this.callback = callback;
    }
    
    public void setHvacOperationComplete() {
        isIdle = true;
        if (callback != null) {
            callback.onTransitionToIdle();
        }
    }
}

嗯,这里要注意:IdlingResource一定要在测试开始前注册,否则Espresso不会等待它。我曾经因为这个bug,排查了整整一个下午。

18.3 模拟器调试:没有实车也能干活

说实话,不是每个团队都能随时拿到实车。所以模拟器调试就成了必备技能。AAOS官方提供了专门的模拟器镜像,你可以在Android Studio里直接创建。

模拟器配置要点:

  • 选择 Automotive (Generic) 设备类型
  • 系统镜像选择 Android Automotive OS 版本
  • 分配至少 2GB RAM1GB 存储
  • 开启 GPU 加速(否则界面卡到你怀疑人生)

模拟器启动后,你会发现它模拟了一个车载中控屏。你可以用鼠标模拟触摸操作,用键盘模拟物理按键。但有个问题——模拟器没有真实的硬件。比如你点空调按钮,它不会真的制冷。

那怎么办?我一般用 ADB 命令 来模拟硬件事件:

# 模拟车辆速度变化
adb shell dumpsys car_service --set-property vehicle_speed 60

# 模拟车门状态
adb shell dumpsys car_service --set-property door_state 1

# 查看当前车辆属性
adb shell dumpsys car_service --list-properties

我的经验: 模拟器调试时,多关注 logcat 输出。我习惯用 adb logcat -s CarService 过滤出车载相关的日志。很多问题在logcat里一眼就能看出来。

18.4 实车测试注意事项:别把车搞坏了

终于到了实车测试环节。说实话,第一次上车测试时,我手心都是汗。生怕一个bug把车机搞死机,或者更糟——把空调搞坏了。

下面是我总结的几条铁律:

注意事项 说明
备份原厂固件 上车前一定要备份车机的原始固件。万一刷坏了,还能恢复。
使用调试版本 不要直接跑release版本。调试版本有更详细的日志输出,方便定位问题。
监控系统资源 adb shell topdumpsys meminfo 监控CPU和内存。车载系统资源有限,别让应用吃太多。
注意安全 测试涉及车辆控制的功能(如刹车、转向)时,一定要在安全环境下进行。最好有专人监护。
日志采集 上车前准备好日志采集脚本。我习惯用 adb logcat -b all -v threadtime > car_log.txt 保存所有日志。

我曾经犯过的错: 有一次在实车上测试空调控制,代码里有个死循环,结果车机CPU跑到100%,整个中控屏卡死了。更惨的是,空调一直吹冷风,车里冻得跟冰窖似的。从那以后,我每次上车测试前都会先跑一遍 压力测试,确保代码不会跑飞。

18.5 总结一下

测试与调试,说白了就是给代码上保险。单元测试保底层逻辑,UI测试保交互流程,模拟器调试保开发效率,实车测试保最终体验。四者缺一不可。

我个人习惯是:代码写完先跑单元测试,然后跑UI测试,接着在模拟器上跑一遍,最后才上车。每一步都能帮你省下不少时间。你想想看,如果在单元测试阶段就发现了一个空指针,那比在实车上发现要省事多了。

好了,这一章就聊到这儿。记住:测试不是浪费时间,而是在帮你省钱


公众号:蓝海资料掘金营,微信deep3321