Android startActivityForResult交互逻辑单元测试方案
针对Activity导航与startActivityForResult流程的稳定单元测试方案
你遇到的仪器化测试flaky问题本质是这类测试依赖真机/模拟器的系统调度、生命周期异步分发、UI渲染时序,CI环境设备资源抢占、动画开关、系统版本差异都会导致随机失败。完全可以用Robolectric框架在本地JVM层完成所有逻辑校验,不需要依赖物理设备/模拟器,执行速度快、稳定性极强,完全覆盖你需要的所有校验点。
核心测试思路
不需要真的完成两个Activity的页面跳转,通过Robolectric提供的Shadow类捕获启动Intent、模拟Activity生命周期、模拟返回结果分发,逐段校验逻辑正确性:
- 捕获ActivityA发出的启动Intent,校验forResult启动模式、requestCode、携带参数
- 单独构建ActivityB实例,传入捕获的Intent,校验初始参数赋值、开关状态
- 模拟用户操作Switch,校验变量更新
- 捕获ActivityB返回时设置的resultCode和结果Intent
- 手动将结果分发给ActivityA的onActivityResult,校验UI状态更新
具体实现代码
首先在模块级build.gradle添加测试依赖(仅testImplementation,不需要androidTest设备侧依赖):
testImplementation 'junit:junit:4.13.2' testImplementation 'org.robolectric:robolectric:4.10.3' testImplementation 'com.google.truth:truth:1.1.3' testImplementation 'androidx.test.ext:junit:1.1.5'
测试类放在src/test/java目录下,代码如下:
@RunWith(RobolectricTestRunner::class) class ActivityInteractTest { @Test fun testActivityAAndBInteractionFlow() { // 1. 初始启动ActivityA,校验初始状态 val activityA = Robolectric.buildActivity(ActivityA::class.java).setup().get() val initialStarVisibility = activityA.starImageView.isVisible // 2. 点击跳转按钮,捕获启动的Intent val shadowA = shadowOf(activityA) val expectedRequestCode = 1 activityA.findViewById<Button>(R.id.goToB_btn).performClick() // 校验确实以forResult模式启动ActivityB val startedIntent = shadowA.nextStartedActivityForResult assertThat(startedIntent.requestCode).isEqualTo(expectedRequestCode) assertThat(startedIntent.intent.component?.className).isEqualTo(ActivityB::class.java.name) // 校验Intent携带的参数正确 assertThat(startedIntent.intent.getBooleanExtra("starVisibility", !initialStarVisibility)) .isEqualTo(initialStarVisibility) // 3. 模拟启动ActivityB,校验初始状态 val activityB = Robolectric.buildActivity(ActivityB::class.java, startedIntent.intent) .setup().get() // 校验starVisibility初始取值正确 assertThat(activityB.starVisibility).isEqualTo(initialStarVisibility) val switch = activityB.findViewById<Switch>(R.id.visibilitySwitch) assertThat(switch.isChecked).isEqualTo(initialStarVisibility) // 4. 切换Switch开关,校验状态更新 val targetVisibility = !initialStarVisibility switch.isChecked = targetVisibility assertThat(activityB.starVisibility).isEqualTo(targetVisibility) // 5. 模拟按返回键,捕获返回结果 val shadowB = shadowOf(activityB) activityB.onBackPressed() // 校验B已经触发finish assertThat(activityB.isFinishing).isTrue() // 校验返回的resultCode和Intent数据 val resultIntent = shadowB.resultIntent assertThat(shadowB.resultCode).isEqualTo(Activity.RESULT_OK) assertThat(resultIntent.getBooleanExtra("starVisibility", !targetVisibility)) .isEqualTo(targetVisibility) // 6. 将结果分发给A,校验星星可见性更新 activityA.onActivityResult(expectedRequestCode, shadowB.resultCode, resultIntent) assertThat(activityA.starImageView.isVisible).isEqualTo(targetVisibility) } }
方案优势
- 无flaky问题:所有逻辑在JVM层同步执行,没有设备异步调度、UI渲染延迟、系统状态干扰,CI环境执行和本地结果完全一致
- 执行速度快:单测试用例执行时间在100ms以内,远快于仪器化测试
- 覆盖全校验点:所有你需要验证的Intent参数、变量取值、返回结果、UI更新逻辑都能覆盖
注意:如果需要验证UI交互的真实渲染效果,可以保留极少量核心路径的仪器化测试,把逻辑校验全部放在Robolectric本地测试中,从根源上解决CI不稳定问题。
内容的提问来源于stack exchange,提问作者Dorrin-sot
相关产品推荐
相关产品推荐

