多模块Android项目JUnit5并行测试下CoroutineTestExtension失效问题求助
多模块Android项目JUnit5并行测试下CoroutineTestExtension失效问题求助
兄弟,这问题我太熟了!你碰到的并发错误根源就俩:
- 你用了
BeforeAllCallback/AfterAllCallback这种类级别的生命周期钩子,整个测试类只设置一次Dispatchers.Main; Dispatchers.setMain()是全局静态操作,完全不是线程安全的,并行测试时多个测试类同时修改它,必然触发"concurrently modified"的报错。
单个测试类跑没问题是因为没有并发冲突,但整个项目/模块并行跑时,多个测试类的beforeAll/afterAll会同时执行,直接把全局Dispatcher的状态搞乱了。
下面给你两个靠谱的解决方案,选哪个都行:
方案1:改造自定义Extension,适配并行测试
把你的Extension改成方法级生命周期钩子,加上同步锁保证线程安全,同时给每个测试方法独立的TestScope:
@OptIn(ExperimentalCoroutinesApi::class) class CoroutineTestExtension( @Suppress("ForbiddenImport") coroutineTestDispatcher: TestDispatcher = kotlinx.coroutines.test.UnconfinedTestDispatcher() ) : Extension, BeforeEachCallback, AfterEachCallback { val testDispatcher: TestDispatcher = coroutineTestDispatcher // 每个测试方法初始化独立的TestScope,避免状态共享 lateinit var testScope: TestScope // 全局锁,保证修改Dispatchers.Main的操作是原子性的 private val dispatcherLock = Any() override fun beforeEach(context: ExtensionContext?) { // 加锁避免多个线程同时修改Main Dispatcher synchronized(dispatcherLock) { Dispatchers.setMain(testDispatcher) } // 为当前测试方法创建专属的TestScope testScope = TestScope(testDispatcher) } override fun afterEach(context: ExtensionContext?) { // 先取消当前测试的Scope,清理协程 testScope.cancel() // 同样加锁重置Main Dispatcher synchronized(dispatcherLock) { Dispatchers.resetMain() } } }
这样改的好处:
- 每个测试方法执行前都会设置一次Main Dispatcher,执行后立刻重置,不会影响其他测试;
- 同步锁保证同一时间只有一个线程在修改
Dispatchers.Main,彻底解决并发冲突; - 每个测试方法用独立的TestScope,避免不同测试之间的协程状态污染。
方案2:直接用官方的CoroutineTest扩展(更省心)
其实kotlinx-coroutines-test早就提供了官方的JUnit5扩展,已经帮我们处理好了并行测试的线程安全和隔离问题,完全不用自己造轮子!
首先在模块的build.gradle里添加依赖(用最新版本就行):
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3"
然后有两种使用方式:
- 在单个测试类上标注:
@ExtendWith(CoroutineTestExtension::class) class MyViewModelTest { // 测试代码... }
- 全局注册,让所有测试类自动使用(多模块项目可以在根build.gradle里配置):
test { useJUnitPlatform { extensions { registerExtension(kotlinx.coroutines.test.junit5.CoroutineTestExtension()) } } }
官方扩展会自动给每个测试方法分配独立的TestDispatcher和TestScope,而且内部已经处理了线程安全,比自己写的靠谱多了。
最后再提两个注意点
- 确保你的测试类默认使用
@TestInstance(Lifecycle.PER_METHOD)(JUnit5默认就是这个),这样每个测试方法会有独立的Extension实例,避免共享状态; - 多模块项目里尽量统一使用同一种扩展方式,要么全用自定义改造后的,要么全用官方的,避免不同模块的扩展逻辑冲突。
内容来源于stack exchange
相关产品推荐
相关产品推荐

