gpt4 book ai didi

android - 测试 LiveData 转换?

转载 作者:塔克拉玛干 更新时间:2023-11-01 21:28:29 26 4
gpt4 key购买 nike

我使用 Android 架构组件和响应式方法构建了启动画面。我从 Preferences LiveData 对象返回 fun isFirstLaunchLD(): SharedPreferencesLiveData<Boolean> .我有将 LiveData 传递给 View 并更新首选项的 ViewModel

val isFirstLaunch = Transformations.map(preferences.isFirstLaunchLD()) { isFirstLaunch ->
if (isFirstLaunch) {
preferences.isFirstLaunch = false
}
isFirstLaunch
}

在我的 Fragment 中,我从 ViewModel 观察 LiveData

    viewModel.isFirstLaunch.observe(this, Observer { isFirstLaunch ->
if (isFirstLaunch) {
animationView.playAnimation()
} else {
navigateNext()
}
})

我现在想测试我的 ViewModel,看看 isFirstLaunch 是否正确更新。我怎样才能测试它?我是否正确分离了所有图层?您会在此示例代码上编写什么样的测试?

最佳答案

Have I separated all layers correctly?

这些层似乎合理地分开了。逻辑在 ViewModel 中,你是 not referring to storing Android Views/Fragments/Activities in the ViewModel .

What kind of tests would you write on this sample code?

测试 ViewModel 时,您可以在此代码上编写检测或纯单元测试。对于单元测试,您可能需要弄清楚如何为首选项创建一个测试替身,以便您可以专注于 isFirstLaunch/map 行为。一种简单的方法是将伪造的偏好测试替身传递到 ViewModel。

How can I test it?

我写了一些关于测试 LiveData 转换的简介,请继续阅读!

测试实时数据转换

Tl;DR 您可以测试 LiveData 转换,您只需要确保观察到转换的结果 LiveData。

事实 1:如果未观察到数据,LiveData 不会发出数据。LiveData 的“lifecycle awareness”就是为了避免额外的工作。 LiveData 知道它的观察者(通常是 Activities/Fragments)处于什么生命周期状态。这允许 LiveData 知道它是否被屏幕上的任何实际观察到。如果未观察到 LiveData 或者它们的观察者在屏幕外,则不会触发观察者(不会调用观察者的 onChanged 方法)。这很有用,因为它可以防止您做额外的工作,例如“更新/显示”屏幕外的 fragment 。

事实 2:必须观察转换生成的 LiveData 才能触发转换。要触发转换,必须观察结果 LiveData(在本例中为 isFirstLaunch)。同样,在没有观察的情况下,不会触发 LiveData 观察器,也不会触发转换。

当您对 ViewModel 进行单元测试时,您不应该拥有或需要访问 Fragment/Activity。如果不能以正常方式设置观察者,如何进行单元测试?

事实 3:在您的测试中,您不需要 LifecycleOwner 来观察 LiveData,您可以使用 observeForever 您不需要生命周期观察者来测试 LiveData。这很令人困惑,因为通常在测试之外(即在您的生产代码中),您将使用 LifecycleObserver像 Activity 或 Fragment。

在测试中,您可以使用 LiveData 方法 observeForever()给没有生命周期所有者的观察者。由于没有 LifecycleOwner,此观察者“始终”观察并且没有开/关屏幕的概念。因此,您必须使用 removeObserver(observer) 手动移除观察者。

综上所述,您可以使用 observeForever 来测试您的转换代码:

class ViewModelTest {

// Executes each task synchronously using Architecture Components.
// For tests and required for LiveData to function deterministically!
@get:Rule
val rule = InstantTaskExecutorRule()


@Test
fun isFirstLaunchTest() {

// Create observer - no need for it to do anything!
val observer = Observer<Boolean> {}

try {
// Sets up the state you're testing for in the VM
// This affects the INPUT LiveData of the transformation
viewModel.someMethodThatAffectsFirstLaunchLiveData()

// Observe the OUTPUT LiveData forever
// Even though the observer itself doesn't do anything
// it ensures any map functions needed to calculate
// isFirstLaunch will be run.
viewModel.isFirstLaunch.observeForever(observer)

assertEquals(viewModel.isFirstLaunch.value, true)
} finally {
// Whatever happens, don't forget to remove the observer!
viewModel.isFirstLaunch.removeObserver(observer)
}
}

}

一些注意事项:

  • 您需要使用 InstantTaskExecutorRule()让您的 LiveData 更新同步执行。你需要 androidx.arch.core:core-testing:<current-version>使用此规则。
  • 虽然您会经常看到 observeForever在测试代​​码中,它有时也会进入生产代码。请记住,当您使用 observeForever 时在生产代码中,您失去了生命周期意识的好处。您还必须确保不要忘记移除观察者!

最后,如果您要编写大量此类测试,则 try、observe-catch-remove-code 可能会变得乏味。如果您使用的是 Kotlin,则可以创建一个扩展函数来简化代码并避免忘记移除观察者的可能性。有两种选择:

选项 1

/**
* Observes a [LiveData] until the `block` is done executing.
*/
fun <T> LiveData<T>.observeForTesting(block: () -> Unit) {
val observer = Observer<T> { }
try {
observeForever(observer)
block()
} finally {
removeObserver(observer)
}
}

这将使测试看起来像:

class ViewModelTest {

@get:Rule
val rule = InstantTaskExecutorRule()


@Test
fun isFirstLaunchTest() {

viewModel.someMethodThatAffectsFirstLaunchLiveData()

// observeForTesting using the OUTPUT livedata
viewModel.isFirstLaunch.observeForTesting {

assertEquals(viewModel.isFirstLaunch.value, true)

}
}

}

选项 2

@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
time: Long = 2,
timeUnit: TimeUnit = TimeUnit.SECONDS,
afterObserve: () -> Unit = {}
): T {
var data: T? = null
val latch = CountDownLatch(1)
val observer = object : Observer<T> {
override fun onChanged(o: T?) {
data = o
latch.countDown()
this@getOrAwaitValue.removeObserver(this)
}
}
this.observeForever(observer)

try {
afterObserve.invoke()

// Don't wait indefinitely if the LiveData is not set.
if (!latch.await(time, timeUnit)) {
throw TimeoutException("LiveData value was never set.")
}

} finally {
this.removeObserver(observer)
}

@Suppress("UNCHECKED_CAST")
return data as T
}

这将使测试看起来像:

class ViewModelTest {

@get:Rule
val rule = InstantTaskExecutorRule()

@Test
fun isFirstLaunchTest() {

viewModel.someMethodThatAffectsFirstLaunchLiveData()

// getOrAwaitValue using the OUTPUT livedata
assertEquals(viewModel.isFirstLaunch.getOrAwaitValue(), true)

}
}

这些选项均取自 reactive branch of Architecture Blueprints .

关于android - 测试 LiveData 转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51810330/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com