【Jetpack Compose进阶学与练】第19课 单元测试与UI测试;ViewModel单元测试 + Compose界面测试基础
2026/9/24 17:31:05 网站建设 项目流程

前言

前面课程主要聚焦业务开发;软件项目开发离不开测试。
本节课学习区分单元测试(JVM本地) 和Instrumented仪器化UI测试(Android设备/模拟器);ViewModel单元测试;Compose的界面测试基础,使用test‑junit4以及compose‑testing。

知识点清单

  1. 区分单元测试(test目录,JVM运行,无设备) vs 仪器化测试(androidTest目录,模拟器/真机执行)
  2. ViewModel单元测试:测试业务逻辑、状态流转,使用Mock模拟仓库
  3. Compose UI测试:ComposeTestRule,节点查找、断言、模拟点击
  4. 测试中隔离业务,避免真实网络请求
  5. 简单测试用例示例

build.gradle 依赖

testImplementation "junit:junit:4.13.2" testImplementation "org.mockito:mockito-core:5.14.2" androidTestImplementation "androidx.test.ext:junit:1.2.1" androidTestImplementation "androidx.compose.ui:ui-test-junit4:1.7.3" debugImplementation "androidx.compose.ui:ui-test-manifest:1.7.3"

完整示例源码

importandroidx.lifecycle.ViewModelimportandroidx.lifecycle.viewModelScopeimportkotlinx.coroutines.flow.MutableStateFlowimportkotlinx.coroutines.flow.StateFlowimportkotlinx.coroutines.launch//业务ViewModel,计数器示例classCounterViewModel:ViewModel{privateval_count=MutableStateFlow(0)valcount:StateFlow<Int>=_countfunincrease(){viewModelScope.launch{_count.value=_count.value+1}}fundecrease(){viewModelScope.launch{_count.value=_count.value-1}}}/** * ViewModel本地单元测试,放在 test/ 目录(JVM,不需要模拟器) */importkotlinx.coroutines.test.runTestimportorg.junit.Assert.assertEqualsimportorg.junit.TestclassCounterViewModelTest{@TestfuntestIncrease()=runTest{valvm=CounterViewModel()vm.increase()assertEquals(1,vm.count.value)}@TestfuntestDecrease()=runTest{valvm=CounterViewModel()vm.decrease()assertEquals(-1,vm.count.value)}}/** * Compose UI仪器化测试示例,androidTest目录,模拟器运行 */importandroidx.compose.ui.test.junit4.createComposeRuleimportandroidx.compose.ui.test.onNodeWithTextimportandroidx.compose.ui.test.performClickimportorg.junit.Ruleimportorg.junit.TestclassCounterScreenUiTest{@get:RulevalcomposeTestRule=createComposeRule()@TestfunuiCounter_clickAdd_increaseNumber(){//挂载Compose页面composeTestRule.setContent{//此处挂载对应的Counter UI页面}//模拟点击“+”按钮composeTestRule.onNodeWithText("+").performClick()//断言界面上文本显示数字1composeTestRule.onNodeWithText("1").assertExists()}}

逐行解读

  1. 两套测试目录区别
  • src/test :本地单元测试,直接跑在电脑JVM上,不需要Android设备;适合ViewModel、Repository工具类业务逻辑;但是不能调用Android系统SDKAPI;Android框架类需要mock。
  • src/androidTest :仪器化测试;运行于模拟器或者真机上面;可以真实执行Compose渲染;用于UI自动化测试;可以查找界面节点、模拟点击、输入文本、做界面断言。
  1. ViewModel单元测试要点
    ViewModel里面大量使用协程 viewModelScope ;正式单元测试的时候要用 runTest 替换真实的Android主调度器;

runTest是kotlin‑coroutines‑test提供;虚拟时间,快进协程,不需要sleep等待。
测试原则:尽量隔离,不要访问真实网络、真实数据库;使用mock模拟仓库返回数据;保证用例稳定,不受后端环境影响。

  1. Compose UI测试核心 ComposeTestRule
    @get:Rule val composeTestRule = createComposeRule()
  • setContent{}:把要测试的Compose组件挂载到测试环境;
  • onNodeWithText(“+”):通过文本找到UI上组件节点;
  • performClick():模拟用户点击;
  • assertExists() / assertDoesNotExist():界面断言,判断该UI节点是否出现;

也可以使用 onNodeWithTag ,给组件设置 Modifier.testTag(“btn_add”) ,优先使用testTag,避免后续修改文字导致测试用例失效。

  1. test‑manifest(debugImplementation)
    ui‑test‑manifest 只在debug编译生效;提供Compose测试所需要的Activity;不要加到release依赖,打包会报错。
  2. 测试的策略建议
    分层测试思路:
    ①优先写ViewModel业务逻辑单元测试(test目录,速度很快);
    ②Repository层测试;
    ③少量关键页面流程补充Compose UI仪器化测试;
    UI测试运行速度慢,模拟器开销大,不要把全部测试都写UI自动化;重点业务流程覆盖即可。

容易踩坑

  1. ❌ 在test本地单元测试直接调用Android SDK类(Context,Toast);会直接抛异常;本地JVM没有Android框架。
  2. ❌ release版本引入ui‑test‑manifest;打包编译报错;必须是debugImplementation。
  3. ❌ UI测试大量依靠文本查找节点;后续产品修改按钮文字,直接导致全部UI用例失效;优先使用Modifier.testTag标记控件。
  4. ❌ 测试用例里面写Thread.sleep()等待网络;应当mock Repository返回假数据,消除网络等待。
  5. ✅ test目录:ViewModel业务单元测试(JVM);androidTest目录:Compose界面仪器化测试;runTest虚拟协程时间;优先testTag定位UI节点;mock隔离外部IO与网络。

多元化习题(练习)

习题1【基础‑改错题】

想在src/test目录单元测试里面直接调用Toast.makeText(),运行直接报错,为什么?
答案:
src/test运行环境是电脑本地JVM,没有Android系统框架;不能直接使用Android SDK API;如果必须要,需要mock模拟。
解读:
本地单元测试适合纯kotlin业务逻辑;Android系统相关逻辑放到仪器化测试。

习题2【基础‑代码修改题】

给一个Button设置测试标记testTag,用于UI查找节点(只写Modifier片段)
答案:

Modifier.testTag("btn_increase")

解读:
测试代码使用onNodeWithTag(“btn_increase”)查找该控件,不受按钮显示文字改动影响。

习题3【进阶‑补充代码题】

ViewModel测试当中runTest{}作用是什么?
答案:
runTest提供虚拟协程调度器;接管协程执行;可以快进虚拟时间,不需要真实sleep等待延时任务;专门用于协程相关单元测试。
解读:
viewModelScope内部启动的协程在runTest环境下可以被测试框架管控。

习题4【综合‑逻辑应用题】

项目当中为什么不要全部写UI自动化测试,优先ViewModel单元测试?
答案:
ViewModel单元测试跑于本地JVM,速度快;不需要模拟器;稳定性好;适合反复回归业务逻辑;UI仪器化测试需要真机/模拟器,运行速度慢,页面改动之后容易导致测试脚本失效;所以优先对业务逻辑层写单元测试;UI自动化只覆盖少量核心业务流程。
解读:
分层测试,底层逻辑优先,上层UI为辅。

习题5【概念简答题】

mock模拟Repository层返回数据,主要目的是什么?
答案:
隔离真实后端网络;测试不再依赖后端服务;不管后端是否可用,测试用例都可以稳定执行;可以方便构造成功、失败、异常多种返回场景。
解读:
解除外部环境依赖是单元测试的关键思想。

本节课知识点总结

  1. 两个测试目录:src/test本地JVM单元测试;src/androidTest真机/模拟器仪器化UI测试。
  2. ViewModel协程业务逻辑单元测试,使用runTest虚拟协程时间;尽量mock仓库隔离网络与IO。
  3. Compose UI测试依靠ComposeTestRule;setContent挂载组件;查找节点,模拟点击,界面断言;优先使用testTag,不依赖显示文本。
  4. ui‑test‑manifest只能debugImplementation;不能release打包引入。
  5. 测试分层策略:优先ViewModel、Repository业务单元测试;少量核心流程补充UI自动化测试,不要全部依赖UI测试。

下一课预告:第20课 KMP Compose‑Multiplatform基础;一套UI跨Android、桌面

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询