MiCarSettings 车控单元测试一期 — 设计方案与测试规范 v1.0
本文档由《框架设计方案 v2.1》与《测试规范指导文档 v1.0》合并而成,涵盖框架架构设计、Controller 类型分析、测试维度定义、测试基类实现、213 个测试用例规范、以及执行与报告体系。
日期:2026-07-07 | CL:#345130(主体)+ #348752(修复)| 一期结果:191 cases / 163 passed / 100% 通过率
一、设计原则
1.1 测试金字塔
┌─────────────┐
│ E2E / UI │ ← 少量关键路径(台架验证)
├─────────────┤
│ Instrument- │ ← 真机信号验证(adb lshal debug)
│ ation │
├─────────────┤
│ JVM 单元 │ ← 主力:Mockito + Truth(快速反馈)
│ 测试 │
└─────────────┘
JVM 层 :主力测试层,无 Android 依赖,CI 秒级反馈
Instrumentation 层 :台架设备验证信号通路,adb lshal debug 注入
E2E 层 :UiAutomator 关键路径冒烟
1.2 设计原则
原则 说明 测试维度标准化 每个设置项按 D1-D7 七维度覆盖 基类复用 四类 Controller 各有测试基类,子类只需 override 参数 信号 Mock 统一 JVM 层 spyk+mockk,Instrumentation 层 adb lshal debug 配置驱动 测试参数通过注解/配置注入,非硬编码 渐进式覆盖 一期 20 个 P0 Controller,二期扩展至 63 个
二、技术架构
2.1 技术栈
组件 选型 版本 测试框架 JUnit 5 5.10.x Mock 框架 Mockito 5 + MockK 1.13 — 断言库 Google Truth 1.4.x 参数化 JUnit 5 @ParameterizedTest — Android Mock TestContextFactory(一期)/ Robolectric(二期) — 覆盖率 JaCoCo 0.8.x
2.2 目录结构
settingsPage/micarLockSettings/
├── src/main/java/.../lock/
│ ├── WalkAwayLockController.kt # 被测 Controller
│ └── ...
├── src/test/java/.../lock/
│ ├── WalkAwayLockControllerTest.kt # JVM 单元测试
│ └── ...
└── src/androidTest/java/.../lock/
├── WalkAwayLockInstrumentedTest.kt # Instrumentation 测试
└── ...
docs/test-infra/
├── gen_test_skeleton.py # AI 测试骨架生成器
├── gen-tests.md # Skill 模板
├── test-dependencies.gradle # 统一依赖配置
└── setup_all_modules_test.sh # 一键部署脚本
三、Controller 类型分析
3.1 四种基类类型
类型 基类 构造函数签名 信号交互模式 Switch BaseVehicleNewSwitchPrefController(Context, String)obtainSetPropertyId() → setCarProperty(isChecked)Tab BaseVehicleTabPreferenceController(Context, String)buildTabData() → onSelectTabChanged(tab)SeekBar/Progress BaseVehicleProgressPrefController(Context, String)progressToPropVal(seekProgress) → updateVehicleProperty()ToggleGroup BaseVehicleButtonToggleGroupPropController / BaseVehicleButtonIconToggleGroupPropController(Context, String)onSwitchTabTo(button, state) → setCarProperty()
3.2 信号交互流程
用户操作 → handlePreferenceChanged(pref, newVal)
→ setCarProperty(propId, value, areaId)
→ CarPropertyManager.setCarProperty()
→ VHAL → ECU
信号回调 → onHandlePropertyChange(prop, update)
→ 解析 prop.value + prop.status
→ 更新 UI 状态(isChecked / selectedIndex / progress)
四、测试维度定义(D1-D7)
D1:信号映射验证
验证信号值到 UI 状态的正确映射。
Controller 类型 验证内容 Switch isPropertyOpen(propVal) → isCheckedTab propValToTabIndex(propVal) → selectedTabIndexProgress progressToPropVal(progress) ↔ propVal 双向转换ToggleGroup 信号值 → 按钮选中状态
D2:可用性验证
验证信号不可用、配置字禁用、License 未授权等场景下设置项的禁用状态。
场景 验证方式 信号不可用 注入 STATUS_ERROR / STATUS_UNAVAILABLE 配置字禁用 Mock CarConfigManager.isXxxEnabled() = false License 未授权 Mock License 检查返回 false 依赖信号不可用 关联信号注入不可用状态
D3:用户操作验证
验证用户操作后信号下发的正确性。
Controller 类型 操作入口方法 Switch handlePreferenceChanged(pref, true/false)Tab onSelectTabChanged(tab)Progress updateVehicleProperty(seekProgress)ToggleGroup onSwitchTabTo(button, state)
D4:二次确认
验证需要二次确认弹窗的设置项(如离车自动锁车)。
D5:异常处理
场景 验证内容 越界信号值 超出枚举/范围的信号值不应导致 crash 信号超时 3 秒无回调后 UI 回弹到操作前状态 并发操作 快速连续切换不应状态混乱
D6:联动控制
验证设置项之间的依赖关系(如氛围灯开关控制亮度滑块可用性)。
D7:中间态
验证 Loading 态、等待确认态等中间状态的 UI 表现。
五、测试基类设计
5.1 JVM 测试基类
BaseVehicleSwitchTest
abstract class BaseVehicleSwitchTest<T : BaseVehicleNewSwitchPrefController> {
protected lateinit var mContext: Context
protected var controller: T? = null
protected var prefChangeListener: OnPreferenceChangeListener? = null
protected var lastCheckedValue: Boolean = false
abstract fun createController(): T
abstract val propId: Int
@BeforeEach open fun setUp() {
Looper.prepare()
mContext = TestContextFactory.create()
controller = createController()
// 通过反射注入 preference 并注册 listener
// 通过反射注入 mCarPropertyManager spy
}
// 信号注入
fun injectSignal(propId: Int, value: Int, status: Int = AVAILABLE)
fun injectUnavailableSignal(propId: Int)
// 用户操作
fun userToggle(checked: Boolean)
// 验证
fun verifyPropertyWritten(expectedValue: Int)
val isChecked: Boolean get() = lastCheckedValue
val isEnabled: Boolean get() = /* preference.isEnabled */
}
BaseVehicleTabTest
abstract class BaseVehicleTabTest<T : BaseVehicleTabPreferenceController> {
abstract fun createController(): T
abstract val propId: Int
// Tab 操作
fun selectTab(index: Int)
val selectedTabIndex: Int
val tabValues: List<Int>
val allTabsDisabled: Boolean
// 信号注入(同 Switch)
fun injectSignal(propId: Int, value: Int, status: Int = AVAILABLE)
fun verifyPropertyWritten(expectedValue: Int)
}
BaseVehicleSeekBarTest
abstract class BaseVehicleSeekBarTest<T : BaseVehicleProgressPrefController> {
abstract fun createController(): T
abstract val propId: Int
// SeekBar 操作
fun simulateUserDrag(targetProgress: Int)
val currentProgress: Int
val isSliderEnabled: Boolean
// 信号注入 + 验证(同上)
}
BaseVehicleToggleGroupTest
abstract class BaseVehicleToggleGroupTest<T : BaseVehicleButtonToggleGroupPropController> {
abstract fun createController(): T
abstract val propId: Int
// 按钮组操作
fun selectButton(index: Int)
val selectedButtonIndex: Int
val buttonValues: List<Int>
val groupEnabled: Boolean
// 信号注入 + 验证(同上)
}
5.2 Instrumentation 测试基础设施
MiCarSwitchMatcher
MiCarNewSwitchPreference 继承 BasicPreference 而非 SwitchPreference,标准 isChecked() 匹配器无法使用。自定义 ViewMatcher:
object MiCarSwitchMatcher {
fun isChecked(expected: Boolean): BoundedMatcher<View, View> {
return object : BoundedMatcher<View, View>(View::class.java) {
override fun matchesSafely(view: View): Boolean {
val switchView = view.findViewById<Switch>(R.id.switch_widget)
return switchView?.isChecked == expected
}
override fun describeTo(description: Description) {
description.appendText("MiCarSwitch isChecked = $expected")
}
}
}
}
ShellMockPropertyUtil
通过 adb lshal debug 注入车辆信号:
object ShellMockPropertyUtil {
fun setProperty(propId: Int, value: Int, areaId: Int = 0) {
val cmd = "lshal debug android.hardware.automotive.vehicle@2.0::IVehicle " +
"--inject-event ${propId} i ${value} ${areaId}"
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
.executeShellCommand(cmd)
}
}
PropertySetCheckHelper
异步验证信号下发结果:
object PropertySetCheckHelper {
fun waitForPropertySet(
propId: Int,
expectedValue: Int,
timeoutMs: Long = 3000
): Boolean {
val latch = CountDownLatch(1)
// 注册 CarPropertyManager 回调
// latch.await(timeoutMs)
// 返回是否在超时前收到预期值
}
}
BaseCarSettingPageTest
所有 Instrumentation 测试的基类:
abstract class BaseCarSettingPageTest {
@get:Rule val activityRule = ActivityScenarioRule(SettingsActivity::class.java)
@Before open fun setUp() {
device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
ShellMockPropertyUtil.resetAll() // 重置所有信号到默认值
}
}
六、核心测试模式
模式 A:开关类信号映射(D1)
@Test
fun signalOn_switchChecked() {
injectSignal(propId, CommonParams.OnOff.ON)
assertThat(isChecked).isTrue()
}
@Test
fun signalOff_switchUnchecked() {
injectSignal(propId, CommonParams.OnOff.OFF)
assertThat(isChecked).isFalse()
}
模式 B:信号不可用(D2)
@Test
fun signalUnavailable_switchDisabled() {
injectUnavailableSignal(propId)
assertThat(isEnabled).isFalse()
}
模式 C:用户操作 → 信号下发(D3)
@Test
fun userTurnsOn_propertyWritten() {
userToggle(true)
verifyPropertyWritten(CommonParams.OnOff.ON)
}
模式 D:环境配置 Mock
// JVM 层
mockkObject(CarConfigManager)
every { CarConfigManager.isXxxEnabled() } returns true
// Instrumentation 层
adb shell setprop persist.sys.xxx.enabled true
adb shell am force-stop com.android.car.settings
七、信号下发超时检测机制
object SignalTimeoutChecker {
const val DEFAULT_TIMEOUT_MS = 3000L
suspend fun setPropertyAndWait(
manager: CarPropertyManager,
propId: Int,
value: Int,
areaId: Int,
timeoutMs: Long = DEFAULT_TIMEOUT_MS
): Boolean {
val latch = CountDownLatch(1)
val callback = object : CarPropertyManager.CarPropertyEventCallback {
override fun onChangeEvent(event: CarPropertyValue<*>) {
if (event.propertyId == propId) latch.countDown()
}
override fun onErrorEvent(propId: Int, zone: Int) { latch.countDown() }
}
manager.registerCallback(callback, propId, CarPropertyManager.SENSOR_RATE_ONCHANGE)
manager.setProperty(propId, value, areaId)
val received = latch.await(timeoutMs, TimeUnit.MILLISECONDS)
manager.unregisterCallback(callback)
return received
}
}
超时测试 Case:
@Test
fun `signal timeout reverts UI state`() {
every { controller.setVehicleProperty(any(), any(), any()) } returns true
controller.handlePreferenceChanged(mockPreference, true)
assertSwitchChecked(true)
Thread.sleep(SignalTimeoutChecker.DEFAULT_TIMEOUT_MS + 500)
assertSwitchChecked(false) // 回弹
}
八、用例编写规范
8.1 命名规范
Case ID 格式 :TC-{模块缩写}-{功能类型}{序号}
模块 缩写 示例 车辆控制 VC TC-VC-SW01(Switch)/ TC-VC-TB01(Tab) 灯光 LG TC-LG-SW01 / TC-LG-PG01(Progress) 门窗锁 DL TC-DL-SW01 / TC-DL-BG01(ButtonGroup) 通用模式 CM TC-CM-SW01
测试方法命名 :维度编号_场景描述_预期结果
@Test fun D101_signalOn_switchChecked() { ... }
@Test fun D201_signalUnavailable_switchDisabled() { ... }
@Test fun D301_userTurnsOn_propertyWritten() { ... }
8.2 Case 结构模板
/**
* 测试用例:{Case ID} — {场景描述}
* 测试维度:D{N}
* 前置条件:{前置条件}
* 预期结果:{预期结果}
*/
@Test
fun `D101 signal mapping - ON value`() {
// Arrange — 注入信号
injectSignal(propId, CommonParams.OnOff.ON)
// Act — (信号回调自动触发 UI 更新)
// Assert — 验证 UI 状态
assertThat(isChecked).isTrue()
}
九、业务特征分析
9.1 八大核心业务特征
特征 说明 测试影响 实时信号驱动 信号值变化即时反映到 UI D1 信号映射验证 分区域控制 不同区域(前排/后排/全车)独立控制 areaId 参数化 状态互斥 部分状态互斥(如驾驶模式切换) D5 边界值测试 二次确认 危险操作需弹窗确认 D4 确认流程测试 信号依赖 部分功能依赖其他信号状态 D2 可用性验证 超时回弹 信号下发超时后 UI 恢复 D5 异常处理 配置字控制 功能开关由配置字控制 D2 配置字 Mock 多车型适配 不同车型信号 ID 不同 参数化 propId
9.2 四层控制策略
┌─────────────────────────────────────────┐
│ Layer 4: 业务逻辑层 │
│ 二次确认 / 超时回弹 / 联动控制 │
├─────────────────────────────────────────┤
│ Layer 3: 信号映射层 │
│ propVal ↔ UI state 双向转换 │
├─────────────────────────────────────────┤
│ Layer 2: 可用性层 │
│ 信号状态 / 配置字 / License 检查 │
├─────────────────────────────────────────┤
│ Layer 1: 硬件抽象层 │
│ CarPropertyManager / VHAL │
└─────────────────────────────────────────┘
9.3 车辆状态矩阵
维度 状态值 测试影响 挡位 P / R / N / D 部分功能仅 P 挡可用 车速 0 / 低速 / 高速 高速禁用部分功能 电源 OFF / ACC / ON / READY 电源状态影响信号可用性 充电 未充电 / 充电中 充电状态影响充电口盖控制
十、一期测试用例清单
共 213 个测试用例,覆盖 3 个模块 20 个 Controller。
10.1 车辆控制模块(62 cases)
10.1.1 离车自动锁车(WalkAwayLockController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW01 D1 信号值 ON → 开关打开 P0 TC-DL-SW02 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW03 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW04 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW05 D2 License 未授权 → 设置项置灰 P1 TC-DL-SW06 D2 依赖信号不可用 → 设置项置灰 P1 TC-DL-SW07 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW08 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW09 D5 越界信号值 → 不改变 UI 状态 P1 TC-DL-SW10 D5 信号超时 → UI 回弹 P1 TC-DL-SW11 D5 快速连续切换 → 最终状态一致 P2
10.1.2 靠近解锁(ApproachingUnlockController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW12 D1 信号值 ON → 开关打开 P0 TC-DL-SW13 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW14 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW15 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW16 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW17 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW18 D5 信号超时 → UI 回弹 P1
10.1.3 P挡自动锁车(PGearAutoLockController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW19 D1 信号值 ON → 开关打开 P0 TC-DL-SW20 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW21 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW22 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW23 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW24 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW25 D5 信号超时 → UI 回弹 P1
10.1.4 锁车自动关窗(CloseWindowWhenLockController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW26 D1 信号值 ON → 开关打开 P0 TC-DL-SW27 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW28 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW29 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW30 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW31 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW32 D5 信号超时 → UI 回弹 P1
10.1.5 儿童锁(ChildLockCustomController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW33 D1 信号值 ON → 开关打开 P0 TC-DL-SW34 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW35 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW36 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW37 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW38 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW39 D5 信号超时 → UI 回弹 P1 TC-DL-SW40 D5 仅 P 挡可用 → 非 P 挡操作无效 P1
10.1.6 电动尾门(ElectricTailGatePreferenceController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW41 D1 信号值 ON → 开关打开 P0 TC-DL-SW42 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW43 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW44 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW45 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW46 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW47 D5 信号超时 → UI 回弹 P1 TC-DL-SW48 D7 开启/关闭中 → Loading 态显示 P1
10.1.7 后视镜自动下翻(MirrorAutoDownController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW49 D1 信号值 ON → 开关打开 P0 TC-DL-SW50 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW51 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW52 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW53 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW54 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW55 D5 信号超时 → UI 回弹 P1
10.1.8 后视镜折叠(MirrorFoldWhenLockController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW56 D1 信号值 ON → 开关打开 P0 TC-DL-SW57 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW58 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW59 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW60 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW61 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW62 D5 信号超时 → UI 回弹 P1
10.2 灯光模块(45 cases)
10.2.1 迎宾灯(WelcomeLightSwitchPrefController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-LG-SW01 D1 信号值 ON → 开关打开 P0 TC-LG-SW02 D1 信号值 OFF → 开关关闭 P0 TC-LG-SW03 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-LG-SW04 D2 配置字禁用 → 设置项隐藏 P0 TC-LG-SW05 D3 用户打开 → 下发 ON 信号 P0 TC-LG-SW06 D3 用户关闭 → 下发 OFF 信号 P0 TC-LG-SW07 D5 信号超时 → UI 回弹 P1 TC-LG-SW08 D5 越界信号值 → 不改变 UI 状态 P1
10.2.2 自动大灯(HighBeamAutoAdjustPrefController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-LG-SW09 D1 信号值 ON → 开关打开 P0 TC-LG-SW10 D1 信号值 OFF → 开关关闭 P0 TC-LG-SW11 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-LG-SW12 D2 配置字禁用 → 设置项隐藏 P0 TC-LG-SW13 D3 用户打开 → 下发 ON 信号 P0 TC-LG-SW14 D3 用户关闭 → 下发 OFF 信号 P0 TC-LG-SW15 D5 信号超时 → UI 回弹 P1
10.2.3 大灯延时(HeadLightsDelayTabLayoutPrefController)— Tab 类型
Case ID 维度 测试场景 优先级 TC-LG-TB01 D1 信号值 0 → 选中”关” P0 TC-LG-TB02 D1 信号值 1 → 选中”15秒” P0 TC-LG-TB03 D1 信号值 2 → 选中”30秒” P0 TC-LG-TB04 D1 信号值 3 → 选中”60秒” P0 TC-LG-TB05 D1 信号状态 UNAVAILABLE → 全部 Tab 置灰 P0 TC-LG-TB06 D2 配置字禁用 → 设置项隐藏 P0 TC-LG-TB07 D3 用户选择”30秒” → 下发信号值 2 P0 TC-LG-TB08 D3 用户选择”关” → 下发信号值 0 P0 TC-LG-TB09 D5 越界信号值 99 → 不改变选中状态 P1 TC-LG-TB10 D5 信号超时 → Tab 恢复原选中 P1
10.2.4 氛围灯亮度(AtmosphereLightBrightnessProgressPrefController)— Progress 类型
Case ID 维度 测试场景 优先级 TC-LG-PG01 D1 信号值 0 → 进度条在最左 P0 TC-LG-PG02 D1 信号值 100 → 进度条在最右 P0 TC-LG-PG03 D1 信号值 50 → 进度条在中间 P0 TC-LG-PG04 D1 信号状态 UNAVAILABLE → 进度条置灰 P0 TC-LG-PG05 D2 配置字禁用 → 设置项隐藏 P0 TC-LG-PG06 D3 用户拖到 70 → 下发信号值 70 P0 TC-LG-PG07 D3 用户拖到 0 → 下发信号值 0 P0 TC-LG-PG08 D5 信号值 -1 → 钳位到 0 P1 TC-LG-PG09 D5 信号值 150 → 钳位到 100 P1 TC-LG-PG10 D5 信号超时 → 进度条回弹 P1 TC-LG-PG11 D7 拖动中 → 显示实时数值 P2
10.2.5 钥匙背景灯亮度(KeyBgLightLevelPrefController)— Progress 类型
Case ID 维度 测试场景 优先级 TC-LG-PG12 D1 信号值 0 → 进度条在最左 P0 TC-LG-PG13 D1 信号值 100 → 进度条在最右 P0 TC-LG-PG14 D1 信号状态 UNAVAILABLE → 进度条置灰 P0 TC-LG-PG15 D2 配置字禁用 → 设置项隐藏 P0 TC-LG-PG16 D3 用户拖到 50 → 下发信号值 50 P0 TC-LG-PG17 D5 越界信号值 → 钳位处理 P1 TC-LG-PG18 D5 信号超时 → 进度条回弹 P1
10.2.6 外部灯光(ExteriorLightsTabLayPreferenceController)— Tab 类型
Case ID 维度 测试场景 优先级 TC-LG-TB11 D1 信号值 → 对应 Tab 选中 P0 TC-LG-TB12 D1 信号状态 UNAVAILABLE → 全部 Tab 置灰 P0 TC-LG-TB13 D2 配置字禁用 → 设置项隐藏 P0 TC-LG-TB14 D3 用户切换 Tab → 下发对应信号 P0 TC-LG-TB15 D5 越界信号值 → 不改变选中状态 P1 TC-LG-TB16 D5 信号超时 → Tab 恢复原选中 P1 TC-LG-TB17 D6 联动信号不可用 → Tab 置灰 P1 TC-LG-TB18 D7 信号下发中 → Tab 显示加载态 P2
10.2.7 大灯伴我回家(FollowMeHomePrefController)— Tab 类型
Case ID 维度 测试场景 优先级 TC-LG-TB19 D1 信号值 → 对应 Tab 选中 P0 TC-LG-TB20 D1 信号状态 UNAVAILABLE → 全部 Tab 置灰 P0 TC-LG-TB21 D3 用户切换 Tab → 下发对应信号 P0 TC-LG-TB22 D5 信号超时 → Tab 恢复原选中 P1
10.2.8 氛围灯开关(MoodLightSwitchPrefController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-LG-SW16 D1 信号值 ON → 开关打开 P0 TC-LG-SW17 D1 信号值 OFF → 开关关闭 P0 TC-LG-SW18 D2 配置字禁用 → 设置项隐藏 P0 TC-LG-SW19 D3 用户打开 → 下发 ON 信号 P0 TC-LG-SW20 D3 用户关闭 → 下发 OFF 信号 P0 TC-LG-SW21 D6 开关关闭 → 亮度滑块置灰 P0 TC-LG-SW22 D5 信号超时 → UI 回弹 P1
10.3 门窗锁模块(86 cases)
门窗锁模块的 Controller 与车辆控制模块中的 Lock 类 Controller 重叠,以下补充独立的门窗锁设置项。
10.3.1 手套箱(GloveBoxController)— ToggleGroup 类型
Case ID 维度 测试场景 优先级 TC-DL-BG01 D1 信号值 0 → 选中”关闭”按钮 P0 TC-DL-BG02 D1 信号值 1 → 选中”打开”按钮 P0 TC-DL-BG03 D1 信号状态 UNAVAILABLE → 按钮组置灰 P0 TC-DL-BG04 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-BG05 D3 用户点击”打开” → 下发信号值 1 P0 TC-DL-BG06 D3 用户点击”关闭” → 下发信号值 0 P0 TC-DL-BG07 D5 信号超时 → 按钮恢复原选中 P1
10.3.2 尾门高度(TailGateHeightPrefController)— Progress 类型
Case ID 维度 测试场景 优先级 TC-DL-PG01 D1 信号值 0 → 进度条最左(最低) P0 TC-DL-PG02 D1 信号值 100 → 进度条最右(最高) P0 TC-DL-PG03 D1 信号值 50 → 进度条中间 P0 TC-DL-PG04 D1 信号状态 UNAVAILABLE → 进度条置灰 P0 TC-DL-PG05 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-PG06 D3 用户拖到 80 → 下发信号值 80+OFFSET P0 TC-DL-PG07 D5 信号值超下界 → 钳位处理 P1 TC-DL-PG08 D5 信号值超上界 → 钳位处理 P1 TC-DL-PG09 D5 信号超时 → 进度条回弹 P1 TC-DL-PG10 D7 拖动中 → 显示实时数值 P2
10.3.3 门锁模式(DoorModeLockController)— Tab 类型
Case ID 维度 测试场景 优先级 TC-DL-TB01 D1 信号值 0 → 选中”自动” P0 TC-DL-TB02 D1 信号值 1 → 选中”手动” P0 TC-DL-TB03 D1 信号状态 UNAVAILABLE → 全部 Tab 置灰 P0 TC-DL-TB04 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-TB05 D3 用户选择”手动” → 下发信号值 1 P0 TC-DL-TB06 D3 用户选择”自动” → 下发信号值 0 P0 TC-DL-TB07 D5 越界信号值 → 不改变选中状态 P1 TC-DL-TB08 D5 信号超时 → Tab 恢复原选中 P1 TC-DL-TB09 D6 关联信号不可用 → Tab 置灰 P1
10.3.4 充电口盖(ChargePortController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW63 D1 信号值 ON → 开关打开 P0 TC-DL-SW64 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW65 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW66 D2 充电中 → 设置项特殊状态 P0 TC-DL-SW67 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW68 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW69 D5 信号超时 → UI 回弹 P1
10.3.5 后视镜设置(MirrorSettingController)— ToggleGroup 类型
Case ID 维度 测试场景 优先级 TC-DL-BG08 D1 信号值 → 对应按钮选中 P0 TC-DL-BG09 D1 信号状态 UNAVAILABLE → 按钮组置灰 P0 TC-DL-BG10 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-BG11 D3 用户切换按钮 → 下发对应信号 P0 TC-DL-BG12 D5 信号超时 → 按钮恢复原选中 P1
10.3.6 便捷进出(EasyEntrySwitchPrefController)— Switch 类型
Case ID 维度 测试场景 优先级 TC-DL-SW70 D1 信号值 ON → 开关打开 P0 TC-DL-SW71 D1 信号值 OFF → 开关关闭 P0 TC-DL-SW72 D1 信号状态 UNAVAILABLE → 开关置灰 P0 TC-DL-SW73 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-SW74 D3 用户打开 → 下发 ON 信号 P0 TC-DL-SW75 D3 用户关闭 → 下发 OFF 信号 P0 TC-DL-SW76 D5 信号超时 → UI 回弹 P1 TC-DL-SW77 D7 座椅移动中 → Loading 态显示 P2
10.3.7 雨刮调节(WiperAdjustTabLayPrefController)— Tab 类型
Case ID 维度 测试场景 优先级 TC-DL-TB10 D1 信号值 → 对应 Tab 选中 P0 TC-DL-TB11 D1 信号状态 UNAVAILABLE → 全部 Tab 置灰 P0 TC-DL-TB12 D2 配置字禁用 → 设置项隐藏 P0 TC-DL-TB13 D3 用户切换 Tab → 下发对应信号 P0 TC-DL-TB14 D5 越界信号值 → 不改变选中状态 P1 TC-DL-TB15 D5 信号超时 → Tab 恢复原选中 P1
10.4 通用测试模式(20 cases)
以下模式适用于所有 Controller 类型,按类型参数化执行。
10.4.1 Switch 通用模式
Case ID 维度 测试场景 优先级 TC-CM-SW01 D1 信号状态 AVAILABLE → 正常映射 P0 TC-CM-SW02 D1 信号状态 ERROR → UI 置灰 P0 TC-CM-SW03 D2 配置字开关切换 → 可见性变化 P0 TC-CM-SW04 D3 快速连续操作 → 最终状态一致 P1 TC-CM-SW05 D5 并发信号回调 → 无 crash P1
10.4.2 Tab 通用模式
Case ID 维度 测试场景 优先级 TC-CM-TB01 D1 Tab 数据构建 → 选项正确 P0 TC-CM-TB02 D1 信号值越界 → 默认选中第一项 P0 TC-CM-TB03 D3 选择相同 Tab → 不重复下发 P1 TC-CM-TB04 D5 Tab 数据为空 → 不 crash P1
10.4.3 Progress 通用模式
Case ID 维度 测试场景 优先级 TC-CM-PG01 D1 最小值映射 P0 TC-CM-PG02 D1 最大值映射 P0 TC-CM-PG03 D1 offset 计算正确 P0 TC-CM-PG04 D5 拖动到边界值 → 钳位处理 P1 TC-CM-PG05 D7 拖动中松手 → 信号下发时机正确 P1
10.4.4 ToggleGroup 通用模式
Case ID 维度 测试场景 优先级 TC-CM-BG01 D1 按钮数据构建 → 选项正确 P0 TC-CM-BG02 D1 信号值越界 → 无按钮选中 P0 TC-CM-BG03 D3 禁用按钮点击 → 不下发信号 P1 TC-CM-BG04 D5 并发按钮点击 → 无 crash P1 TC-CM-BG05 D7 按钮切换中 → 加载态显示 P2
十一、Skill 快速生成 Case 指南
11.1 输入参数
参数 必填 说明 示例 controller_class是 Controller 类名 WalkAwayLockControllerbase_class是 基类类型 Switch / Tab / ButtonGroup / Progresssignal_prop_id是 信号属性ID MiCarPropertyIds.LockCtrl.WALK_AWAY_LOCKban_reason_prop_id否 禁止原因信号ID MiCarPropertyIds.LockCtrl.WALK_AWAY_LOCK_BAN_REASONneed_confirm否 是否需要二次确认 true / falsehas_child_prefs否 是否有子设置项 true / falsetab_values否 Tab选项值列表 [0, 1, 2]special_conditions否 特殊业务条件 ["临时开关状态", "车主模式验证"]area_id否 区域ID 0x10
11.2 输出规范
JVM 单元测试文件 (src/test/)— 继承对应基类,包含 D1-D7 适用维度
Instrumentation 测试文件 (src/androidTest/,可选)— 真机验证场景
Case 清单表格 — Case ID、场景描述、优先级、预期结果
11.3 自动生成脚本
# 生成测试骨架(dry-run 模式预览)
python3 docs/test-infra/gen_test_skeleton.py --dry-run \
settingsPage/micarLockSettings/src/main/java/.../WalkAwayLockController.kt
# 实际生成并写入 src/test/
python3 docs/test-infra/gen_test_skeleton.py \
settingsPage/micarLockSettings/src/main/java/.../WalkAwayLockController.kt
支持的基类映射:
BaseVehicleNewSwitchPrefController → Switch 模板
BaseVehicleTabPreferenceController → Tab 模板
BaseVehicleProgressPrefController / BaseVehicleSeekBarPreferenceController → SeekBar 模板
BaseVehicleButtonToggleGroupPropController / BaseVehicleButtonIconToggleGroupPropController → ToggleGroup 模板
十二、测试执行与报告
12.1 执行命令
# JVM 单元测试(全量)
./gradlew testXcdDebugUnitTest
# 单模块
./gradlew :settingsPage:micarLightSettings:testXcdDebugUnitTest
# 单个测试类
./gradlew :settingsPage:micarLightSettings:testXcdDebugUnitTest \
--tests "*.light.WelcomeLightSwitchPrefControllerTest"
# Instrumentation 测试(需台架设备)
./gradlew connectedDebugAndroidTest
12.2 报告路径
测试层 HTML 报告 XML 报告 JVM {module}/build/reports/tests/testXcdDebugUnitTest/index.html{module}/build/test-results/testXcdDebugUnitTest/TEST-*.xmlInstrumentation {module}/build/reports/androidTests/connected/index.html{module}/build/outputs/androidTest-results/connected/TEST-*.xml
12.3 覆盖率收集
./gradlew :settingsPage:micarLightSettings:jacocoTestReport
# 报告: {module}/build/reports/jacoco/jacocoTestReport/html/index.html
12.4 Daily CI 报告体系
组件 路径 功能 run_daily_tests.sh docs/test-infra/ 执行全量测试并收集结果 generate_daily_summary.py docs/test-infra/ 生成测试报告摘要 update_trend.py docs/test-infra/ 更新通过率趋势图 notify_feishu.sh docs/test-infra/ 飞书 webhook 通知
十三、一期覆盖范围与执行结果
13.1 一期目标与实际
模块 设置项数 计划 Case 实际 Case 通过 跳过 通过率 micarLightSettings 6 ~50 67 67 0 100% micarLockSettings 8 ~80 56 39 17 100% VehicleBodyControl 6 ~60 68 57 11 100% 合计 20 ~190 191 163 28 100%
13.2 跳过用例说明
28 个跳过用例原因均为需 Robolectric 环境(mock Context 无法提供完整 Android 框架行为):
Controller 跳过数 原因 ElectricTailGatePreferenceController 9 电动尾门控制需 Activity + View 系统 MirrorSettingController 9 后视镜设置需 Activity 生命周期 ChildLockCustomController 5 儿童锁自定义需 Robolectric DoorModeLockController 3 门锁模式部分 case 需 View 系统 GloveBoxController 2 手套箱控制部分 case 需 Activity
13.3 执行策略
阶段 内容 执行频率 开发阶段 JVM 单元测试 每次提交 集成阶段 JVM + Instrumentation 每日构建 发版阶段 全量回归 发版前
十四、已知限制与规避
限制 影响 规避方式 配置字 lazy 缓存 配置字禁用测试可能不可靠 测试前 setprop + am force-stop 杀进程 sendProperty STATUS_UNAVAILABLE 不稳定信号不可用测试可能误判 在 CarPropertyUtil 中验证 mock status 实现 弹窗 UiAutomator 依赖 跨进程弹窗可能抓取失败 使用 UiDevice.wait() 等待弹窗出现 台架设备信号延迟 Instrumentation 测试可能超时 适当增加 SIGNAL_WAIT_MS Controller 生命周期依赖 onCreateInternal 未调用时 mCarPropertyManager 为 nullJVM 测试用 spyk 拦截 setVehicleProperty mock Context 无完整 Android 行为 28 个 case 需跳过 二期引入 Robolectric
十五、二期展望
目标 详情 Controller 覆盖 三模块全量 63 个(新增 43 个) Case 总量 ~708 个(新增 ~517 个) 技术升级 引入 Robolectric 回收 28 个跳过用例 共享 test library 抽取 33 个重复文件为共享模块 CI 门禁 Gerrit CI + 飞书通知 分批策略 5 个 Batch(Robolectric → P0 Switch → P0 Tab → P1 全类型 → P2 + CI)
附录 A:设置项清单模板
setting:
name: "离车自动锁车"
key: "pk_settings_walk_away_lock"
controller: "WalkAwayLockController"
base_class: "Switch" # Switch / Tab / ButtonGroup / Progress
module: "micarLockSettings"
page: "LockSettingsFragment"
signals:
main:
prop_id: "MiCarPropertyIds.LockCtrl.WALK_AWAY_LOCK"
on_value: 1
off_value: 0
ban_reason:
prop_id: "MiCarPropertyIds.LockCtrl.WALK_AWAY_LOCK_BAN_REASON"
enabled: true
behavior:
need_confirm: false
has_intermediate_state: false
has_child_prefs: false
timeout_ms: 3000
area_id: 0
附录 B:Controller 基类方法速查
方法 所在基类 用途 测试覆盖 obtainSetPropertyId()Switch 获取下发信号ID D1 isPropertyOpen(propVal)Switch 信号值→开关状态 D1 isInnerLogicAvailable(prop)Switch 业务可用性判断 D2 needSecondConfirm()Switch 是否需要确认弹窗 D4 showConfirmDialog(isOn)Switch 显示确认弹窗 D4 setCarProperty(isChecked)Switch 下发信号 D3 handlePreferenceChanged(pref, val)Switch/Progress 用户操作真实入口 D3/D4 onHandlePropertyChange(prop, update)Switch 信号回调 D1/D2 needIntermediateState()Switch 是否需要 Loading 态 D7 isInLoadingState(prop)Switch 是否处于 Loading 态 D7 isPreventSwitchSlide(propVal)Switch 是否阻止滑动操作 D7 buildTabData()Tab 构建Tab数据 D1 onSelectTabChanged(tab)Tab Tab 用户操作入口 D3 propValToTabIndex(propVal)Tab 信号值→Tab索引 D1 getPropValueByTab(tab)Tab Tab→信号值 D1 onSwitchTabTo(button, state)ButtonGroup 按钮组用户操作入口 D3 onHandleButtonChecked(button, state)ButtonGroup 按钮切换处理 D3 onClickDisabledTab(tab)ButtonGroup 禁用按钮点击处理 D2 progressToPropVal(seekProgress)Progress 进度值→信号值 D1 updateVehicleProperty(seekProgress)Progress 滑块信号下发 D3
附录 C:信号属性 ID 参考
车辆控制 (DrivingCtrl)
属性名 信号ID 说明 DRIVING_MODE Driving.DRIVING_MODE_STATUS 驾驶模式 ENERGY_REGENERATION Driving.REGENERATION_LEVEL_MODE1_STATUS 能量回收等级 SINGLE_PEDAL Driving.SINGLE_PEDAL_STATUS 单踏板模式 CREEP_MODE Driving.CREEP_MODE_STATUS 蠕行模式 AUTO_HOLD Driving.AUTO_HOLD_STATUS AutoHold STEERING_FEEL Driving.STEERING_FEEL_STATUS 转向手感
灯光 (LightCtrl)
属性名 信号ID 说明 WELCOME_LIGHT Light.WELCOME_LIGHT_STATE 迎宾灯 AUTO_LIGHT Light.AUTO_HEADLIGHT_STATUS 大灯自动 FOLLOW_ME_HOME Light.FOLLOW_ME_HOME_STATUS 跟我回家 MOOD_LIGHT_SWITCH Light.MOOD_LIGHT_SWITCH 氛围灯开关 MOOD_LIGHT_BRIGHTNESS Light.MOOD_LIGHT_BRIGHTNESS 氛围灯亮度
门窗锁 (LockCtrl)
属性名 信号ID 说明 WALK_AWAY_LOCK Door.LEAVING_AUTO_LOCK 离车自动锁车 APPROACHING_UNLOCK Door.APPROACHING_UNLOCK 靠近解锁 P_GEAR_AUTO_LOCK Door.PARK_AUTO_UNLOCK P挡自动解锁 CLOSE_WINDOW_WHEN_LOCK Window.LOCK_AUTO_CLOSE 锁车自动关窗 CHILD_LOCK Door.CHILD_LOCK_STATUS 儿童锁 POWER_TAILGATE Door.POWER_TAILGATE_STATUS 电动尾门 CHARGE_PORT Charging.CHARGE_PORT_STATUS 充电口盖 MIRROR_FOLD MirrorControl.AUTO_FOLD 后视镜折叠
以上信号 ID 为参考值,实际使用时需对照 MiCarPropertyIds.java 确认准确的属性 ID。