第二十二章 自动化测试与CI/CD:UI测试框架、单元测试、持续集成流水线搭建

说实话,车载系统的测试,跟普通App开发完全是两码事。我刚开始做车载项目时,以为写几个单元测试就完事了。结果第一次集成测试,车机黑屏了整整三分钟——就因为一个UI线程的异步回调没处理好。嗯,从那以后,我对自动化测试的态度就彻底变了。

这一章,咱们聊聊车载信息娱乐系统里的自动化测试和CI/CD。说白了,就是怎么让机器替我们干活,保证每次提交的代码不会把车机搞崩。

22.1 车载UI测试的特殊性

车载UI测试跟手机测试最大的区别在哪?我总结了几点:

  • 安全第一:任何UI操作都不能干扰驾驶安全
  • 硬件多样性:不同分辨率、不同触控屏、不同旋钮
  • 实时性要求:启动时间、响应时间都有硬指标
  • 多模态交互:触控+语音+旋钮+方向盘按键

我在项目中遇到过最头疼的事:同一个UI组件,在模拟器上跑得好好的,一上真机就卡顿。后来发现是GPU渲染管线的问题。所以,车载UI测试必须包含真机测试环节。

22.2 UI测试框架选型

目前主流的车载UI测试框架,我整理了一张表:

框架 适用平台 特点 我的评价
Espresso Android Auto Google官方,稳定,API简洁 首选,但需要配合UI Automator
UI Automator Android Auto 跨App测试,支持系统级操作 适合做系统级交互测试
XCUITest CarPlay Apple官方,Swift原生 CarPlay开发必备
Appium 跨平台 支持多语言,社区活跃 适合做集成测试,但速度慢

我个人习惯用Espresso做Android Auto的UI测试。为什么?因为它能精确控制UI线程,这对车载系统特别重要。你想想看,如果测试框架本身就有延迟,你怎么判断UI响应是否达标?

22.2.1 Espresso实战示例

这里给一段我常用的测试代码,用来验证导航应用的启动速度:

@Test
public void testNavigationLaunchTime() {
    // 记录启动时间戳
    long startTime = System.currentTimeMillis();
    
    // 点击导航按钮
    onView(withId(R.id.btn_navigation))
        .perform(click());
    
    // 等待导航界面加载完成
    onView(withId(R.id.map_view))
        .check(matches(isDisplayed()));
    
    // 计算耗时
    long elapsed = System.currentTimeMillis() - startTime;
    
    // 车载导航启动必须在2秒内
    assertThat(elapsed, lessThan(2000L));
}
小技巧: 我建议在测试用例里加上时间断言。车载UI的响应时间是有国标要求的,比如启动时间不能超过2秒。把这些硬指标写进测试用例,比写在文档里管用多了。

22.3 单元测试:打好地基

单元测试是自动化测试的基石。但很多车载开发团队不重视它,觉得「UI能跑就行」。我曾经接手过一个项目,没有单元测试,每次改个数据模型都要全量回归测试,累死人。

车载系统的单元测试,我重点关注三个方向:

  1. 数据层测试:CAN总线数据解析、传感器数据融合
  2. 业务逻辑测试:导航路径计算、媒体播放状态机
  3. 状态管理测试:驾驶模式切换、多屏互动逻辑

22.3.1 用JUnit测试数据解析

举个例子,解析CAN总线传来的车速数据:

public class VehicleSpeedParserTest {
    
    @Test
    public void testParseSpeed() {
        // 模拟CAN数据帧
        byte[] canData = {0x00, 0x32, 0x00, 0x00}; // 50 km/h
        
        VehicleSpeedParser parser = new VehicleSpeedParser();
        int speed = parser.parse(canData);
        
        assertEquals(50, speed);
    }
    
    @Test
    public void testSpeedRange() {
        // 测试边界值
        byte[] maxData = {0x00, 0xFF, 0x00, 0x00}; // 255 km/h
        
        VehicleSpeedParser parser = new VehicleSpeedParser();
        int speed = parser.parse(maxData);
        
        // 车速不能超过250 km/h(物理限制)
        assertTrue(speed <= 250);
    }
}
注意: 车载系统的单元测试一定要覆盖边界值和异常值。我曾经遇到过一个问题:车速传感器偶尔会返回负值,结果导航界面直接崩溃了。从那以后,我所有数据解析的测试用例都加上了「非法值处理」的测试。

22.4 持续集成流水线搭建

CI/CD在车载开发里,比普通App开发要复杂得多。为什么?因为编译环境、测试环境、部署环境都不一样。

我搭建的流水线一般包含这几个阶段:

车载CI/CD流水线架构 代码提交 Git Push / PR 静态检查 Lint / 代码规范 单元测试 JUnit / XCTest 编译构建 Gradle / Xcode UI自动化测试 Espresso / XCUITest 真机测试 硬件在环测试 性能测试 启动时间 / 内存 打包发布 OTA升级包 每个阶段失败都会阻断流水线,并通知开发人员

22.4.1 Jenkins流水线配置

我用Jenkins比较多,这里给一个简化版的流水线脚本:

pipeline {
    agent any
    
    stages {
        stage('代码检查') {
            steps {
                sh 'gradle lint'
            }
        }
        
        stage('单元测试') {
            steps {
                sh 'gradle test'
            }
            post {
                success {
                    junit '**/build/test-results/*.xml'
                }
            }
        }
        
        stage('UI测试') {
            steps {
                // 启动模拟器
                sh 'emulator -avd car_avd -no-window &'
                sh 'gradle connectedAndroidTest'
            }
        }
        
        stage('真机部署') {
            steps {
                // 部署到测试车机
                sh 'adb connect 192.168.1.100:5555'
                sh 'gradle installDebug'
            }
        }
    }
    
    post {
        failure {
            // 发送通知到企业微信
            sh 'curl -X POST ...'
        }
    }
}
核心要点: 车载CI/CD流水线一定要包含「真机测试」环节。模拟器测试通过不代表真机没问题。我见过太多模拟器上跑得飞起、真机上卡成PPT的案例了。

22.5 避坑指南

做车载自动化测试这几年,我踩过不少坑。分享几个典型的:

  • 不要依赖UI线程的sleep:用IdlingResource代替Thread.sleep,否则测试不稳定
  • 注意屏幕方向:车载屏幕有横屏、竖屏、方形屏,测试用例要覆盖
  • 模拟器分辨率要匹配:用1280x720的模拟器去测1920x1080的UI,布局会乱
  • CI服务器要配GPU:没有GPU加速,UI测试跑得比蜗牛还慢

我曾经犯过一个低级错误:在CI服务器上跑UI测试,结果所有截图都是黑屏。查了半天才发现,服务器没有安装显卡驱动。嗯,这种问题排查起来特别费时间。

22.6 测试报告与质量门禁

自动化测试跑完了,怎么判断代码能不能合入?我一般设置这几个质量门禁:

指标 阈值 说明
单元测试覆盖率 ≥ 80% 核心业务逻辑必须覆盖
UI测试通过率 100% 有一条失败就不合入
启动时间 ≤ 2秒 冷启动时间
内存泄漏 0 LeakCanary检测
ANR次数 0 应用无响应
我的习惯: 每次提交代码前,先在本地跑一遍「快速测试集」——只包含核心功能的测试用例,大概5分钟跑完。等CI服务器上再跑全量测试。这样既保证了效率,又不放过问题。

自动化测试和CI/CD,说白了就是给代码质量上保险。车载系统不像手机App,崩溃了重启就行。车机崩溃,那可是要出安全事故的。所以,别嫌麻烦,把测试做扎实了,比什么都强。


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