车载测试框架:CarTestManager、模拟器测试、硬件在环测试、自动化测试脚本
做车载系统开发,最怕什么?
我最怕的是——代码写完了,但不知道它到底能不能跑。尤其是Android Automotive OS这种牵一发动全身的系统,一个Service挂掉,整个IVI都可能黑屏。所以今天我们来聊聊车载测试框架,说白了就是怎么保证你的代码在车上不出事。
测试框架的核心:CarTestManager
CarTestManager是Android Automotive OS提供的一套测试基础设施。它不是某个具体的测试工具,而是一个管理测试生命周期的框架。我刚开始接触它的时候,觉得它跟普通的Instrumentation测试差不多,后来才发现,它解决了一个关键问题——车载环境下的权限和依赖管理。
举个例子,你要测试一个空调控制功能。普通测试只需要调API就行,但在车上,你得确保CarService已经启动、HAL层已经就绪、甚至某些硬件状态是正常的。CarTestManager就是帮你搞定这些前置条件的。
核心能力:
- 管理CarService的启动与关闭
- 模拟车辆属性(Vehicle Property)的读写
- 控制HAL层的回调行为
- 提供测试专用的权限集
写测试用例时,我习惯这样用:
@RunWith(CarTestManagerJUnit4ClassRunner.class)
public class HVACControllerTest {
@Rule
public CarTestManager mCarTestManager = new CarTestManager();
@Test
public void testSetTemperature() throws Exception {
Car car = mCarTestManager.createCar();
HVACManager hvac = (HVACManager) car.getCarManager(Car.HVAC_SERVICE);
// 设置目标温度
hvac.setTemperature(25.0f);
// 验证属性值
float actual = hvac.getTemperature();
assertEquals(25.0f, actual, 0.1f);
}
}
这里有个坑,我曾经踩过——createCar()方法默认不会启动所有Service。如果你测试的功能依赖某个特定Service,记得用@RequiresCarService注解显式声明。不然测试跑起来,Service没启动,你查半天都不知道问题出在哪。
模拟器测试:低成本验证的第一道防线
模拟器测试,说白了就是在你的开发机上跑一个虚拟的车载系统。它不能替代真机测试,但能帮你快速发现低级错误。
我记得有一次,团队里一个新同事提交了一段代码,在模拟器上跑得好好的,结果上车就崩。后来发现是代码里写死了某个硬件的路径,模拟器上有这个路径,真机上没有。这种问题,模拟器是测不出来的。
那模拟器到底能测什么?
| 测试类型 | 模拟器支持度 | 我的建议 |
|---|---|---|
| UI布局与交互 | 高 | 可以放心用,尤其是多分辨率适配 |
| CarService API调用 | 中 | 基本功能可测,但HAL层行为是模拟的 |
| 车辆属性变化 | 低 | 只能测API调用是否报错,无法验证真实行为 |
| 性能与功耗 | 极低 | 别浪费时间,直接上真机 |
启动模拟器时,我建议用-gpu swiftshader_indirect参数。为什么?因为车载系统通常没有GPU直通,用软件渲染更接近真实环境。嗯,这个小技巧是我在调试一个动画卡顿问题时发现的。
硬件在环测试:让测试更真实
硬件在环测试,英文叫Hardware-in-the-Loop,简称HIL。说白了就是把真实的硬件设备接入测试环境,让测试代码跟真实硬件交互。
你想想看,模拟器里你调setTemperature(),它只是改了一个内存里的值。但在HIL测试中,这个调用会真的通过CAN总线发给空调控制器,然后你读取回来的温度值,是真实的物理反馈。
HIL测试的典型架构是这样的:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ 测试脚本 │────▶│ HIL测试平台 │────▶│ 真实硬件设备 │
│ (Python/Java) │ │ (如NI PXI/Vector)│ │ (空调/车窗/灯光)│
└─────────────────┘ └──────────────────┘ └─────────────────┘
│ │ │
│◀───────────────────────│◀────────────────────────│
│ 读取真实状态 │ 采集传感器数据 │
我在项目中遇到过一个问题:某个车型的空调控制,在模拟器上测试全部通过,但HIL测试时发现,温度设定到26度以上时,实际出风温度反而下降了。后来排查发现,是HAL层对温度值的缩放因子搞错了。这种问题,没有HIL测试根本发现不了。
注意:HIL测试的成本很高。一套像样的HIL测试平台,少说几十万。所以别指望每个项目都上HIL。我的经验是:核心安全功能(如制动、转向)必须HIL测试,非安全功能(如娱乐、导航)可以靠模拟器+真机测试覆盖。
自动化测试脚本:把测试变成习惯
手动测试做一次两次还行,但车载系统迭代快,每次改代码都手动测一遍,谁也受不了。所以自动化测试脚本是必须的。
我常用的自动化测试框架是Python + ADB。为什么用Python?因为写起来快,而且跟车载系统的交互主要靠ADB命令,Python的subprocess模块用起来很顺手。
下面是一个简单的自动化测试脚本示例:
import subprocess
import time
def run_car_test():
# 1. 安装测试APK
subprocess.run(["adb", "install", "-r", "HVACTest.apk"])
# 2. 启动测试
subprocess.run([
"adb", "shell", "am", "instrument",
"-w", "com.example.hvac.test/androidx.test.runner.AndroidJUnitRunner"
])
# 3. 拉取测试结果
subprocess.run(["adb", "pull", "/sdcard/test_results.xml", "."])
# 4. 解析结果
with open("test_results.xml", "r") as f:
content = f.read()
if "failure" in content:
print("测试失败,请检查日志")
else:
print("全部通过")
if __name__ == "__main__":
run_car_test()
这个脚本很简单,但实际项目中,我会加上重试机制和日志收集。为什么?因为车载系统有时候会因为总线负载过高导致偶发失败,重试一次可能就过了。但如果是真问题,重试多少次都会失败,这时候日志就派上用场了。
小技巧:自动化测试脚本里,我习惯在每个关键步骤后加一个time.sleep(1)。别小看这一秒,车载系统的响应速度跟手机不一样,很多操作需要等待总线响应。不加这个延时,测试脚本跑得飞快,但结果全是假失败。
三种测试策略的取舍
说了这么多,你可能想问:到底该用哪种测试?
我的建议是分层覆盖:
- 开发阶段:用模拟器测试,快速验证API调用和UI逻辑。这个阶段追求的是速度,不求全面。
- 集成阶段:用自动化测试脚本,在真机上跑回归测试。这个阶段要覆盖所有核心功能。
- 验收阶段:用HIL测试,验证硬件交互的正确性。这个阶段只测高风险功能。
我曾经见过一个团队,把所有测试都压在HIL上,结果每次改代码都要排队等HIL平台,开发效率极低。后来我帮他们调整了策略,把80%的测试放到模拟器和真机上,HIL只用来验证那20%的关键路径。效率一下子就上来了。
知识体系总览
下面这张图,是我对车载测试框架的整体理解。你可以把它当作一个参考,看看自己当前在哪个环节:
这张图把整个测试框架分成了三层。最上面是三种测试手段,中间是各自的核心能力,最下面是自动化脚本把它们串起来。你仔细看会发现,CarTestManager其实贯穿了所有测试类型——不管是模拟器还是HIL,都需要它来管理测试环境。
好了,关于车载测试框架,我就讲这么多。记住一点:测试不是为了找bug,而是为了让你对代码有信心。没有测试的车载代码,就像没有安全带的汽车——也许能开,但出事就是大事。
公众号:蓝海资料掘金营,微信deep3321