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 55.10.x
Mock 框架Mockito 5 + MockK 1.13
断言库Google Truth1.4.x
参数化JUnit 5 @ParameterizedTest
Android MockTestContextFactory(一期)/ Robolectric(二期)
覆盖率JaCoCo0.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 四种基类类型

类型基类构造函数签名信号交互模式
SwitchBaseVehicleNewSwitchPrefController(Context, String)obtainSetPropertyId()setCarProperty(isChecked)
TabBaseVehicleTabPreferenceController(Context, String)buildTabData()onSelectTabChanged(tab)
SeekBar/ProgressBaseVehicleProgressPrefController(Context, String)progressToPropVal(seekProgress)updateVehicleProperty()
ToggleGroupBaseVehicleButtonToggleGroupPropController / 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 类型验证内容
SwitchisPropertyOpen(propVal)isChecked
TabpropValToTabIndex(propVal)selectedTabIndex
ProgressprogressToPropVal(progress)propVal 双向转换
ToggleGroup信号值 → 按钮选中状态

D2:可用性验证

验证信号不可用、配置字禁用、License 未授权等场景下设置项的禁用状态。

场景验证方式
信号不可用注入 STATUS_ERROR / STATUS_UNAVAILABLE
配置字禁用Mock CarConfigManager.isXxxEnabled() = false
License 未授权Mock License 检查返回 false
依赖信号不可用关联信号注入不可用状态

D3:用户操作验证

验证用户操作后信号下发的正确性。

Controller 类型操作入口方法
SwitchhandlePreferenceChanged(pref, true/false)
TabonSelectTabChanged(tab)
ProgressupdateVehicleProperty(seekProgress)
ToggleGrouponSwitchTabTo(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-{模块缩写}-{功能类型}{序号}

模块缩写示例
车辆控制VCTC-VC-SW01(Switch)/ TC-VC-TB01(Tab)
灯光LGTC-LG-SW01 / TC-LG-PG01(Progress)
门窗锁DLTC-DL-SW01 / TC-DL-BG01(ButtonGroup)
通用模式CMTC-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 八大核心业务特征

特征说明测试影响
实时信号驱动信号值变化即时反映到 UID1 信号映射验证
分区域控制不同区域(前排/后排/全车)独立控制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-SW01D1信号值 ON → 开关打开P0
TC-DL-SW02D1信号值 OFF → 开关关闭P0
TC-DL-SW03D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW04D2配置字禁用 → 设置项隐藏P0
TC-DL-SW05D2License 未授权 → 设置项置灰P1
TC-DL-SW06D2依赖信号不可用 → 设置项置灰P1
TC-DL-SW07D3用户打开 → 下发 ON 信号P0
TC-DL-SW08D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW09D5越界信号值 → 不改变 UI 状态P1
TC-DL-SW10D5信号超时 → UI 回弹P1
TC-DL-SW11D5快速连续切换 → 最终状态一致P2

10.1.2 靠近解锁(ApproachingUnlockController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW12D1信号值 ON → 开关打开P0
TC-DL-SW13D1信号值 OFF → 开关关闭P0
TC-DL-SW14D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW15D2配置字禁用 → 设置项隐藏P0
TC-DL-SW16D3用户打开 → 下发 ON 信号P0
TC-DL-SW17D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW18D5信号超时 → UI 回弹P1

10.1.3 P挡自动锁车(PGearAutoLockController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW19D1信号值 ON → 开关打开P0
TC-DL-SW20D1信号值 OFF → 开关关闭P0
TC-DL-SW21D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW22D2配置字禁用 → 设置项隐藏P0
TC-DL-SW23D3用户打开 → 下发 ON 信号P0
TC-DL-SW24D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW25D5信号超时 → UI 回弹P1

10.1.4 锁车自动关窗(CloseWindowWhenLockController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW26D1信号值 ON → 开关打开P0
TC-DL-SW27D1信号值 OFF → 开关关闭P0
TC-DL-SW28D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW29D2配置字禁用 → 设置项隐藏P0
TC-DL-SW30D3用户打开 → 下发 ON 信号P0
TC-DL-SW31D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW32D5信号超时 → UI 回弹P1

10.1.5 儿童锁(ChildLockCustomController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW33D1信号值 ON → 开关打开P0
TC-DL-SW34D1信号值 OFF → 开关关闭P0
TC-DL-SW35D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW36D2配置字禁用 → 设置项隐藏P0
TC-DL-SW37D3用户打开 → 下发 ON 信号P0
TC-DL-SW38D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW39D5信号超时 → UI 回弹P1
TC-DL-SW40D5仅 P 挡可用 → 非 P 挡操作无效P1

10.1.6 电动尾门(ElectricTailGatePreferenceController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW41D1信号值 ON → 开关打开P0
TC-DL-SW42D1信号值 OFF → 开关关闭P0
TC-DL-SW43D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW44D2配置字禁用 → 设置项隐藏P0
TC-DL-SW45D3用户打开 → 下发 ON 信号P0
TC-DL-SW46D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW47D5信号超时 → UI 回弹P1
TC-DL-SW48D7开启/关闭中 → Loading 态显示P1

10.1.7 后视镜自动下翻(MirrorAutoDownController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW49D1信号值 ON → 开关打开P0
TC-DL-SW50D1信号值 OFF → 开关关闭P0
TC-DL-SW51D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW52D2配置字禁用 → 设置项隐藏P0
TC-DL-SW53D3用户打开 → 下发 ON 信号P0
TC-DL-SW54D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW55D5信号超时 → UI 回弹P1

10.1.8 后视镜折叠(MirrorFoldWhenLockController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW56D1信号值 ON → 开关打开P0
TC-DL-SW57D1信号值 OFF → 开关关闭P0
TC-DL-SW58D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW59D2配置字禁用 → 设置项隐藏P0
TC-DL-SW60D3用户打开 → 下发 ON 信号P0
TC-DL-SW61D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW62D5信号超时 → UI 回弹P1

10.2 灯光模块(45 cases)

10.2.1 迎宾灯(WelcomeLightSwitchPrefController)— Switch 类型

Case ID维度测试场景优先级
TC-LG-SW01D1信号值 ON → 开关打开P0
TC-LG-SW02D1信号值 OFF → 开关关闭P0
TC-LG-SW03D1信号状态 UNAVAILABLE → 开关置灰P0
TC-LG-SW04D2配置字禁用 → 设置项隐藏P0
TC-LG-SW05D3用户打开 → 下发 ON 信号P0
TC-LG-SW06D3用户关闭 → 下发 OFF 信号P0
TC-LG-SW07D5信号超时 → UI 回弹P1
TC-LG-SW08D5越界信号值 → 不改变 UI 状态P1

10.2.2 自动大灯(HighBeamAutoAdjustPrefController)— Switch 类型

Case ID维度测试场景优先级
TC-LG-SW09D1信号值 ON → 开关打开P0
TC-LG-SW10D1信号值 OFF → 开关关闭P0
TC-LG-SW11D1信号状态 UNAVAILABLE → 开关置灰P0
TC-LG-SW12D2配置字禁用 → 设置项隐藏P0
TC-LG-SW13D3用户打开 → 下发 ON 信号P0
TC-LG-SW14D3用户关闭 → 下发 OFF 信号P0
TC-LG-SW15D5信号超时 → UI 回弹P1

10.2.3 大灯延时(HeadLightsDelayTabLayoutPrefController)— Tab 类型

Case ID维度测试场景优先级
TC-LG-TB01D1信号值 0 → 选中”关”P0
TC-LG-TB02D1信号值 1 → 选中”15秒”P0
TC-LG-TB03D1信号值 2 → 选中”30秒”P0
TC-LG-TB04D1信号值 3 → 选中”60秒”P0
TC-LG-TB05D1信号状态 UNAVAILABLE → 全部 Tab 置灰P0
TC-LG-TB06D2配置字禁用 → 设置项隐藏P0
TC-LG-TB07D3用户选择”30秒” → 下发信号值 2P0
TC-LG-TB08D3用户选择”关” → 下发信号值 0P0
TC-LG-TB09D5越界信号值 99 → 不改变选中状态P1
TC-LG-TB10D5信号超时 → Tab 恢复原选中P1

10.2.4 氛围灯亮度(AtmosphereLightBrightnessProgressPrefController)— Progress 类型

Case ID维度测试场景优先级
TC-LG-PG01D1信号值 0 → 进度条在最左P0
TC-LG-PG02D1信号值 100 → 进度条在最右P0
TC-LG-PG03D1信号值 50 → 进度条在中间P0
TC-LG-PG04D1信号状态 UNAVAILABLE → 进度条置灰P0
TC-LG-PG05D2配置字禁用 → 设置项隐藏P0
TC-LG-PG06D3用户拖到 70 → 下发信号值 70P0
TC-LG-PG07D3用户拖到 0 → 下发信号值 0P0
TC-LG-PG08D5信号值 -1 → 钳位到 0P1
TC-LG-PG09D5信号值 150 → 钳位到 100P1
TC-LG-PG10D5信号超时 → 进度条回弹P1
TC-LG-PG11D7拖动中 → 显示实时数值P2

10.2.5 钥匙背景灯亮度(KeyBgLightLevelPrefController)— Progress 类型

Case ID维度测试场景优先级
TC-LG-PG12D1信号值 0 → 进度条在最左P0
TC-LG-PG13D1信号值 100 → 进度条在最右P0
TC-LG-PG14D1信号状态 UNAVAILABLE → 进度条置灰P0
TC-LG-PG15D2配置字禁用 → 设置项隐藏P0
TC-LG-PG16D3用户拖到 50 → 下发信号值 50P0
TC-LG-PG17D5越界信号值 → 钳位处理P1
TC-LG-PG18D5信号超时 → 进度条回弹P1

10.2.6 外部灯光(ExteriorLightsTabLayPreferenceController)— Tab 类型

Case ID维度测试场景优先级
TC-LG-TB11D1信号值 → 对应 Tab 选中P0
TC-LG-TB12D1信号状态 UNAVAILABLE → 全部 Tab 置灰P0
TC-LG-TB13D2配置字禁用 → 设置项隐藏P0
TC-LG-TB14D3用户切换 Tab → 下发对应信号P0
TC-LG-TB15D5越界信号值 → 不改变选中状态P1
TC-LG-TB16D5信号超时 → Tab 恢复原选中P1
TC-LG-TB17D6联动信号不可用 → Tab 置灰P1
TC-LG-TB18D7信号下发中 → Tab 显示加载态P2

10.2.7 大灯伴我回家(FollowMeHomePrefController)— Tab 类型

Case ID维度测试场景优先级
TC-LG-TB19D1信号值 → 对应 Tab 选中P0
TC-LG-TB20D1信号状态 UNAVAILABLE → 全部 Tab 置灰P0
TC-LG-TB21D3用户切换 Tab → 下发对应信号P0
TC-LG-TB22D5信号超时 → Tab 恢复原选中P1

10.2.8 氛围灯开关(MoodLightSwitchPrefController)— Switch 类型

Case ID维度测试场景优先级
TC-LG-SW16D1信号值 ON → 开关打开P0
TC-LG-SW17D1信号值 OFF → 开关关闭P0
TC-LG-SW18D2配置字禁用 → 设置项隐藏P0
TC-LG-SW19D3用户打开 → 下发 ON 信号P0
TC-LG-SW20D3用户关闭 → 下发 OFF 信号P0
TC-LG-SW21D6开关关闭 → 亮度滑块置灰P0
TC-LG-SW22D5信号超时 → UI 回弹P1

10.3 门窗锁模块(86 cases)

门窗锁模块的 Controller 与车辆控制模块中的 Lock 类 Controller 重叠,以下补充独立的门窗锁设置项。

10.3.1 手套箱(GloveBoxController)— ToggleGroup 类型

Case ID维度测试场景优先级
TC-DL-BG01D1信号值 0 → 选中”关闭”按钮P0
TC-DL-BG02D1信号值 1 → 选中”打开”按钮P0
TC-DL-BG03D1信号状态 UNAVAILABLE → 按钮组置灰P0
TC-DL-BG04D2配置字禁用 → 设置项隐藏P0
TC-DL-BG05D3用户点击”打开” → 下发信号值 1P0
TC-DL-BG06D3用户点击”关闭” → 下发信号值 0P0
TC-DL-BG07D5信号超时 → 按钮恢复原选中P1

10.3.2 尾门高度(TailGateHeightPrefController)— Progress 类型

Case ID维度测试场景优先级
TC-DL-PG01D1信号值 0 → 进度条最左(最低)P0
TC-DL-PG02D1信号值 100 → 进度条最右(最高)P0
TC-DL-PG03D1信号值 50 → 进度条中间P0
TC-DL-PG04D1信号状态 UNAVAILABLE → 进度条置灰P0
TC-DL-PG05D2配置字禁用 → 设置项隐藏P0
TC-DL-PG06D3用户拖到 80 → 下发信号值 80+OFFSETP0
TC-DL-PG07D5信号值超下界 → 钳位处理P1
TC-DL-PG08D5信号值超上界 → 钳位处理P1
TC-DL-PG09D5信号超时 → 进度条回弹P1
TC-DL-PG10D7拖动中 → 显示实时数值P2

10.3.3 门锁模式(DoorModeLockController)— Tab 类型

Case ID维度测试场景优先级
TC-DL-TB01D1信号值 0 → 选中”自动”P0
TC-DL-TB02D1信号值 1 → 选中”手动”P0
TC-DL-TB03D1信号状态 UNAVAILABLE → 全部 Tab 置灰P0
TC-DL-TB04D2配置字禁用 → 设置项隐藏P0
TC-DL-TB05D3用户选择”手动” → 下发信号值 1P0
TC-DL-TB06D3用户选择”自动” → 下发信号值 0P0
TC-DL-TB07D5越界信号值 → 不改变选中状态P1
TC-DL-TB08D5信号超时 → Tab 恢复原选中P1
TC-DL-TB09D6关联信号不可用 → Tab 置灰P1

10.3.4 充电口盖(ChargePortController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW63D1信号值 ON → 开关打开P0
TC-DL-SW64D1信号值 OFF → 开关关闭P0
TC-DL-SW65D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW66D2充电中 → 设置项特殊状态P0
TC-DL-SW67D3用户打开 → 下发 ON 信号P0
TC-DL-SW68D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW69D5信号超时 → UI 回弹P1

10.3.5 后视镜设置(MirrorSettingController)— ToggleGroup 类型

Case ID维度测试场景优先级
TC-DL-BG08D1信号值 → 对应按钮选中P0
TC-DL-BG09D1信号状态 UNAVAILABLE → 按钮组置灰P0
TC-DL-BG10D2配置字禁用 → 设置项隐藏P0
TC-DL-BG11D3用户切换按钮 → 下发对应信号P0
TC-DL-BG12D5信号超时 → 按钮恢复原选中P1

10.3.6 便捷进出(EasyEntrySwitchPrefController)— Switch 类型

Case ID维度测试场景优先级
TC-DL-SW70D1信号值 ON → 开关打开P0
TC-DL-SW71D1信号值 OFF → 开关关闭P0
TC-DL-SW72D1信号状态 UNAVAILABLE → 开关置灰P0
TC-DL-SW73D2配置字禁用 → 设置项隐藏P0
TC-DL-SW74D3用户打开 → 下发 ON 信号P0
TC-DL-SW75D3用户关闭 → 下发 OFF 信号P0
TC-DL-SW76D5信号超时 → UI 回弹P1
TC-DL-SW77D7座椅移动中 → Loading 态显示P2

10.3.7 雨刮调节(WiperAdjustTabLayPrefController)— Tab 类型

Case ID维度测试场景优先级
TC-DL-TB10D1信号值 → 对应 Tab 选中P0
TC-DL-TB11D1信号状态 UNAVAILABLE → 全部 Tab 置灰P0
TC-DL-TB12D2配置字禁用 → 设置项隐藏P0
TC-DL-TB13D3用户切换 Tab → 下发对应信号P0
TC-DL-TB14D5越界信号值 → 不改变选中状态P1
TC-DL-TB15D5信号超时 → Tab 恢复原选中P1

10.4 通用测试模式(20 cases)

以下模式适用于所有 Controller 类型,按类型参数化执行。

10.4.1 Switch 通用模式

Case ID维度测试场景优先级
TC-CM-SW01D1信号状态 AVAILABLE → 正常映射P0
TC-CM-SW02D1信号状态 ERROR → UI 置灰P0
TC-CM-SW03D2配置字开关切换 → 可见性变化P0
TC-CM-SW04D3快速连续操作 → 最终状态一致P1
TC-CM-SW05D5并发信号回调 → 无 crashP1

10.4.2 Tab 通用模式

Case ID维度测试场景优先级
TC-CM-TB01D1Tab 数据构建 → 选项正确P0
TC-CM-TB02D1信号值越界 → 默认选中第一项P0
TC-CM-TB03D3选择相同 Tab → 不重复下发P1
TC-CM-TB04D5Tab 数据为空 → 不 crashP1

10.4.3 Progress 通用模式

Case ID维度测试场景优先级
TC-CM-PG01D1最小值映射P0
TC-CM-PG02D1最大值映射P0
TC-CM-PG03D1offset 计算正确P0
TC-CM-PG04D5拖动到边界值 → 钳位处理P1
TC-CM-PG05D7拖动中松手 → 信号下发时机正确P1

10.4.4 ToggleGroup 通用模式

Case ID维度测试场景优先级
TC-CM-BG01D1按钮数据构建 → 选项正确P0
TC-CM-BG02D1信号值越界 → 无按钮选中P0
TC-CM-BG03D3禁用按钮点击 → 不下发信号P1
TC-CM-BG04D5并发按钮点击 → 无 crashP1
TC-CM-BG05D7按钮切换中 → 加载态显示P2

十一、Skill 快速生成 Case 指南

11.1 输入参数

参数必填说明示例
controller_classController 类名WalkAwayLockController
base_class基类类型Switch / Tab / ButtonGroup / Progress
signal_prop_id信号属性IDMiCarPropertyIds.LockCtrl.WALK_AWAY_LOCK
ban_reason_prop_id禁止原因信号IDMiCarPropertyIds.LockCtrl.WALK_AWAY_LOCK_BAN_REASON
need_confirm是否需要二次确认true / false
has_child_prefs是否有子设置项true / false
tab_valuesTab选项值列表[0, 1, 2]
special_conditions特殊业务条件["临时开关状态", "车主模式验证"]
area_id区域ID0x10

11.2 输出规范

  1. JVM 单元测试文件src/test/)— 继承对应基类,包含 D1-D7 适用维度
  2. Instrumentation 测试文件src/androidTest/,可选)— 真机验证场景
  3. 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-*.xml
Instrumentation{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.shdocs/test-infra/执行全量测试并收集结果
generate_daily_summary.pydocs/test-infra/生成测试报告摘要
update_trend.pydocs/test-infra/更新通过率趋势图
notify_feishu.shdocs/test-infra/飞书 webhook 通知

十三、一期覆盖范围与执行结果

13.1 一期目标与实际

模块设置项数计划 Case实际 Case通过跳过通过率
micarLightSettings6~5067670100%
micarLockSettings8~80563917100%
VehicleBodyControl6~60685711100%
合计20~19019116328100%

13.2 跳过用例说明

28 个跳过用例原因均为需 Robolectric 环境(mock Context 无法提供完整 Android 框架行为):

Controller跳过数原因
ElectricTailGatePreferenceController9电动尾门控制需 Activity + View 系统
MirrorSettingController9后视镜设置需 Activity 生命周期
ChildLockCustomController5儿童锁自定义需 Robolectric
DoorModeLockController3门锁模式部分 case 需 View 系统
GloveBoxController2手套箱控制部分 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获取下发信号IDD1
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)TabTab 用户操作入口D3
propValToTabIndex(propVal)Tab信号值→Tab索引D1
getPropValueByTab(tab)TabTab→信号值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_MODEDriving.DRIVING_MODE_STATUS驾驶模式
ENERGY_REGENERATIONDriving.REGENERATION_LEVEL_MODE1_STATUS能量回收等级
SINGLE_PEDALDriving.SINGLE_PEDAL_STATUS单踏板模式
CREEP_MODEDriving.CREEP_MODE_STATUS蠕行模式
AUTO_HOLDDriving.AUTO_HOLD_STATUSAutoHold
STEERING_FEELDriving.STEERING_FEEL_STATUS转向手感

灯光 (LightCtrl)

属性名信号ID说明
WELCOME_LIGHTLight.WELCOME_LIGHT_STATE迎宾灯
AUTO_LIGHTLight.AUTO_HEADLIGHT_STATUS大灯自动
FOLLOW_ME_HOMELight.FOLLOW_ME_HOME_STATUS跟我回家
MOOD_LIGHT_SWITCHLight.MOOD_LIGHT_SWITCH氛围灯开关
MOOD_LIGHT_BRIGHTNESSLight.MOOD_LIGHT_BRIGHTNESS氛围灯亮度

门窗锁 (LockCtrl)

属性名信号ID说明
WALK_AWAY_LOCKDoor.LEAVING_AUTO_LOCK离车自动锁车
APPROACHING_UNLOCKDoor.APPROACHING_UNLOCK靠近解锁
P_GEAR_AUTO_LOCKDoor.PARK_AUTO_UNLOCKP挡自动解锁
CLOSE_WINDOW_WHEN_LOCKWindow.LOCK_AUTO_CLOSE锁车自动关窗
CHILD_LOCKDoor.CHILD_LOCK_STATUS儿童锁
POWER_TAILGATEDoor.POWER_TAILGATE_STATUS电动尾门
CHARGE_PORTCharging.CHARGE_PORT_STATUS充电口盖
MIRROR_FOLDMirrorControl.AUTO_FOLD后视镜折叠

以上信号 ID 为参考值,实际使用时需对照 MiCarPropertyIds.java 确认准确的属性 ID。