如何在保留createAndroidComposeRule的同时用Barista规则重跑Flaky测试?
我有一个基于Jetpack Compose的应用,当前基础测试规则配置如下:
val activityComposeRule by lazy { createAndroidComposeRule<MainActivity>() } // 测试失败时的Barista重试规则 val flakyRule = FlakyTestRule().allowFlakyAttemptsByDefault(2) val permissionRule = ... @get:Rule val rulechain: RuleChain = run { RuleChain.outerRule(permissionRule).around(activityComposeRule).around(flakyRule) }
测试代码中使用activityComposeRule进行节点交互:
activityComposeRule.onNode(...)
测试失败后无法重跑,报错内容:
Cannot call setContent twice per test!
已知开发者提供的方案是改用createEmptyComposeRule()配合ActivityScenario<T>,但重构整个代码成本极高。请问是否存在不修改createAndroidComposeRule<MainActivity>,同时使用Barista规则重跑Flaky测试的方法?
附Google问题追踪器类似问题翻译:
这是Jetpack Compose测试框架的已知问题:当
createAndroidComposeRule与重试规则(如Barista的FlakyTestRule)结合使用时,重试过程中框架会尝试再次调用setContent,从而抛出"Cannot call setContent twice per test!"异常。目前官方暂未推出直接修复方案,但社区已有多种规避思路。
无需大规模重构现有代码,可通过以下两种方式实现重试功能:
方案一:调整RuleChain顺序
将flakyRule移至规则链最外层,确保每次重试时重新初始化整个规则链(包括activityComposeRule),而非仅重试内层测试逻辑:
val activityComposeRule by lazy { createAndroidComposeRule<MainActivity>() } val flakyRule = FlakyTestRule().allowFlakyAttemptsByDefault(2) val permissionRule = ... @get:Rule val rulechain: RuleChain = run { RuleChain.outerRule(flakyRule).around(permissionRule).around(activityComposeRule) }
原理:FlakyTestRule作为外层规则时,每次重试都会触发内层规则的重新初始化,activityComposeRule会重新创建MainActivity实例,从根源避免重复调用setContent的问题。
方案二:自定义可重置的ComposeRule包装类
创建自定义规则,在每次重试前重新初始化Compose测试环境,同时保留原有测试代码的调用方式:
class ResettableComposeRule<T : Activity>(private val activityClass: Class<T>) : TestRule { private lateinit var composeRule: AndroidComposeRule<T> override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { // 每次执行前重新创建ComposeRule实例 composeRule = createAndroidComposeRule(activityClass) composeRule.apply(base, description).evaluate() } } } // 暴露原ComposeRule的核心方法,兼容现有测试代码 fun onNode(matcher: SemanticsMatcher): SemanticsNodeInteraction { return composeRule.onNode(matcher) } // 按需添加其他需要的方法,例如onAllNodes、waitForIdle等 }
在测试类中替换原有规则并调整链顺序:
val resettableComposeRule by lazy { ResettableComposeRule(MainActivity::class.java) } val flakyRule = FlakyTestRule().allowFlakyAttemptsByDefault(2) val permissionRule = ... @get:Rule val rulechain: RuleChain = run { RuleChain.outerRule(permissionRule).around(flakyRule).around(resettableComposeRule) } // 测试代码调用方式完全不变 resettableComposeRule.onNode(...)
这种方式仅需替换规则实例,无需修改现有测试中的交互逻辑,重构成本极低。
内容的提问来源于stack exchange,提问作者AShX

