fin*_*usl 6 unit-testing kotlin kotlin-coroutines
我想为我的 android 应用程序编写测试。有时 viewModel 会使用 Kotlins 协程启动函数在后台执行任务。这些任务在 androidx.lifecycle 库提供的 viewModelScope 中执行。为了仍然测试这些功能,我用 Dispatchers.Unconfined 替换了默认的 android Dispatchers,它同步运行代码。
至少在大多数情况下。使用 suspendCoroutine 时, Dispatchers.Unconfined 不会被挂起并稍后恢复,而是会简单地返回。的文档Dispatchers.Unconfined揭示了原因:
[Dispatchers.Unconfined] 让协程在相应挂起函数使用的任何线程中恢复。
所以根据我的理解,协程实际上并没有被挂起,而是之后的异步函数的其余部分suspendCoroutine在调用continuation.resume. 因此测试失败。
例子:
class TestClassTest {
var testInstance = TestClass()
@Test
fun `Test with default dispatchers should fail`() {
testInstance.runAsync()
assertFalse(testInstance.valueToModify)
}
@Test
fun `Test with dispatchers replaced by Unconfined should pass`() {
testInstance.DefaultDispatcher = Dispatchers.Unconfined
testInstance.IODispatcher = Dispatchers.Unconfined
testInstance.runAsync()
assertTrue(testInstance.valueToModify)
}
@Test
fun `I need to also test some functions that use suspend coroutine - How can I do that?`() {
testInstance.DefaultDispatcher = Dispatchers.Unconfined
testInstance.IODispatcher = Dispatchers.Unconfined
testInstance.runSuspendCoroutine()
assertTrue(testInstance.valueToModify)//fails
}
}
class TestClass {
var DefaultDispatcher: CoroutineContext = Dispatchers.Default
var IODispatcher: CoroutineContext = Dispatchers.IO
val viewModelScope = CoroutineScope(DefaultDispatcher)
var valueToModify = false
fun runAsync() {
viewModelScope.launch(DefaultDispatcher) {
valueToModify = withContext(IODispatcher) {
sleep(1000)
true
}
}
}
fun runSuspendCoroutine() {
viewModelScope.launch(DefaultDispatcher) {
valueToModify = suspendCoroutine {
Thread {
sleep(1000)
//long running operation calls listener from background thread
it.resume(true)
}.start()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
我正在尝试使用runBlocking,但是只有当您使用CoroutineScope创建的runBlocking. 但是由于我的代码是在此CoroutineScope提供的情况下启动的,viewModelScope因此将无法正常工作。如果可能的话,我不想在任何地方注入 CoroutineScope,因为任何较低级别的类都可以(并且确实)像示例中那样创建自己的 CoroutineScope,然后测试必须了解很多关于实现的细节。作用域背后的想法是每个类都可以控制取消它们的异步操作。例如, viewModelScope 在 viewModel 被销毁时默认取消。
我的问题:
我可以使用什么协程调度程序来运行启动和 withContext 等阻塞(如Dispatchers.Unconfined)并运行 suspendCoroutine 阻塞?
将协程上下文传递给您的模型怎么样?就像是
class Model(parentContext: CoroutineContext = Dispatchers.Default) {
private val modelScope = CoroutineScope(parentContext)
var result = false
fun doWork() {
modelScope.launch {
Thread.sleep(3000)
result = true
}
}
}
@Test
fun test() {
val model = runBlocking {
val model = Model(coroutineContext)
model.doWork()
model
}
println(model.result)
}
Run Code Online (Sandbox Code Playgroud)
从 androidx.lifecycle更新viewModelScope 你可以只使用这个测试规则
@ExperimentalCoroutinesApi
class CoroutinesTestRule : TestWatcher() {
private val dispatcher: TestCoroutineDispatcher = TestCoroutineDispatcher()
override fun starting(description: Description?) {
super.starting(description)
Dispatchers.setMain(dispatcher)
}
override fun finished(description: Description?) {
super.finished(description)
Dispatchers.resetMain()
dispatcher.cleanupTestCoroutines()
}
}
Run Code Online (Sandbox Code Playgroud)
这是测试模型和测试
class MainViewModel : ViewModel() {
var result = false
fun doWork() {
viewModelScope.launch {
Thread.sleep(3000)
result = true
}
}
}
class MainViewModelTest {
@ExperimentalCoroutinesApi
@get:Rule
var coroutinesRule = CoroutinesTestRule()
private val model = MainViewModel()
@Test
fun `check result`() {
model.doWork()
assertTrue(model.result)
}
}
Run Code Online (Sandbox Code Playgroud)
不要忘记添加testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"以获得TestCoroutineDispatcher
在其他答案的帮助下,我找到了以下解决方案。正如 sedovav 在他的回答中所建议的,我可以异步运行任务并等待 Deferred。基本上是相同的想法,我可以通过启动运行任务并等待处理该任务的作业。这两种情况的问题是,我如何获得延期或工作。
我找到了解决这个问题的方法。这样的 Job 始终是 CoroutineContext 中包含的 Job 的子级,CoroutineContext 是 CoroutineScope 的一部分。所以下面的代码确实解决了这个问题,无论是在我的示例代码中还是在实际的 android 应用程序中。
@Test
fun `I need to also test some functions that use suspend coroutine - How can I do that?`() {
replaceDispatchers()
testInstance.runSuspendCoroutine()
runBlocking {
(testInstance.viewModelScope.coroutineContext[Job]?.children?.forEach { it.join() }
}
assertTrue(testInstance.valueToModify)//fails
}
Run Code Online (Sandbox Code Playgroud)
它看起来确实有点像黑客攻击,所以如果有人知道为什么这是危险的,请告诉我。如果存在由其他类创建的另一个底层 CoroutineScope,它也将不起作用。但这是我拥有的最好的解决方案。
| 归档时间: |
|
| 查看次数: |
7296 次 |
| 最近记录: |