我的单元测试出现问题,原因是我认为是协程中的竞争条件,导致单元测试随机失败。
我有一个类,其中某些依赖项在我的UT中被模拟。 (我同时使用了Mockito和MockK)。单独运行测试时,它们永远不会失败,即使运行完整套件也不会导致测试失败。
但是,如果我进行了gradlew构建或完整的gradlew检查(ktlint + detekt + lint + UT),它们消耗的CPU较多,则其中一项测试会随机失败。
因此,对于某些背景:
我正在测试的班级有一个参与者,它接收两种类型的消息:入队和推入。方法register()
接受一个参数并将其排队,直到经过设置的计时器,然后将其推入。如果再次调用register()
,它将进入新参数,使用计时器取消协程并调用新参数。
suspend fun register(param: Param) {
// This part suspends until the param is added to the DB
val id = dao.addParam(param)
actor.offer(Message.Enqueue(param))
delayJob?.cancelAndJoin()
delayJob = actor.sendDelayed(this, TIMER, Message.Push)
}
sendDelayed()
是我创建的扩展
fun <E> SendChannel<E>.sendDelayed(
scope: CoroutineScope,
time: Long,
element: E
) = scope.launch {
delay(time)
withContext(NonCancellable) {
send(element)
}
}
我正在测试的类的依赖项之一是CoroutineDispatcher
,我在UT中将其设置为Dispatchers.Unconfined
。
例如,失败的测试之一是这样的:
@Mock
private lateinit var mockDao: Dao
private lateinit var classUnderTest: ClassUnderTest
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
classUnderTest = ClassUnderTest(mockDao, Dispatchers.Unconfined)
}
@Test
fun `test that fail randomly`() {
val param = Param()
runBlocking {
whenever(mockDao.addParam(param)) doReturn 1
classUnderTest.register(param)
delay(TIMER)
}
verifyBlocking(mockDao) { update(param) }
}
它以NullPointerException
失败。不知何故模拟不被模拟。
我尝试使用MockK,结果相似,但无法说找不到答案(同样的原因,框架找不到模拟对象)。
在这一点上,我已经尝试消除延迟,更改调度程序等等。看来我不能依赖于为UT启动协程。我有一个类似的问题,一个简单的launch { val id = dao.get(param) }
会导致UT随机失败,因为在检查时未设置id。
在我如何使用协同程序或如何对其进行测试方面,一定存在根本性的错误。非常感谢您的反馈。
谢谢