DiffUtil 浓缩咖啡测试
DiffUtil espresso Test
[测试]我们如何为 DiffUtil 编写测试用例?
val oldList = listOf("a", "b", 0)
val newList = listOf("a", "c", 0, 1)
我们如何编写一个特定的测试用例,即我们的 onBindViewHolder
尚未针对新列表中的项目调用,这些项目相对于旧列表未修改,这基本上是 DiffUtil
所做的让一切顺利?
关于我们如何监听回收站视图本身的变化有什么想法吗?
我正在考虑使用 registerAdapterDataObserver(AdapterDataObserver)
,我们可以覆盖 onItemRangeChanged
并监听变化,但在测试中使用这种方法并不好。
DiffUtil 不会再次重新绑定任何相同的项目,因此根据要求,如果此测试用例 运行 使用 DiffUtil 以外的任何其他实现,它将失败。
@Test
fun testAdapter_noRebinding() {
val adapter = getAdapterObject()
lateinit var dataObserver: RecyclerView.AdapterDataObserver
var doesRebind = false
val oldList = listOf("a", "b", 0)
val newList = listOf("a", "b", 0)
ActivityScenario.launch(AdapterActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
dataObserver = object: RecyclerView.AdapterDataObserver(){
override fun onChanged() {
doesRebind = true
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
assertThat(positionStart).isEqualTo(0)
assertThat(itemCount).isEqualTo(3)
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
doesRebind = true
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
doesRebind = true
}
}
adapter.registerAdapterDataObserver(dataObserver)
}
scenario.onActivity { activity ->
val liveData = getList(activity)
liveData.value = oldList
}
scenario.onActivity { activity ->
val liveData = getList(activity)
liveData.value = newList
}
scenario.onActivity { activity ->
assertThat(doesRebind).isEqualTo(false)
adapter.unregisterAdapterDataObserver(dataObserver)
}
}
}
更新
添加了使用 Mockito 的测试用例,但这些测试用例仅通过 robolectric 而不是 espresso
@Test
fun testBindableAdapter_incomingSameData_noRebindingShouldHappen() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
verify(fakeObserver, times(1)).onItemRangeInserted(0, 3)
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, never()).onChanged()
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_removeOneItem_verifyChangeOnlyOneItem() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeRemoved(0, 1)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_insertOneItem_verifyChangeOnlyOneItem() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeInserted(0, 1)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_moveOneItem_verifyNoRecreatingWholeList() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(INT_VALUE_1, STR_VALUE_0, STR_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeChanged(2, 1, null)
verify(fakeObserver, times(1)).onItemRangeChanged(0, 1, null)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_updateOneItemContent_verifyOneItemUpdated() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeChanged(1, 1, null)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
浓缩咖啡出错
java.lang.RuntimeException: Wanted but not invoked:
adapterDataObserver.onItemRangeRemoved(0, 1);
-> at org.oppia.app.recyclerview.BindableAdapterTest$testBindableAdapter_removeOneItem_verifyChangeOnlyOneItem$$inlined$use$lambda.perform(BindableAdapterTest.kt:303)
Actually, there were zero interactions with this mock.
java.lang.RuntimeException: Wanted but not invoked:
adapterDataObserver.onItemRangeChanged(
2,
1,
null
);
-> at org.oppia.app.recyclerview.BindableAdapterTest$testBindableAdapter_moveOneItem_verifyNoRecreatingWholeList$$inlined$use$lambda.perform(BindableAdapterTest.kt:349)
Actually, there were zero interactions with this mock.
[测试]我们如何为 DiffUtil 编写测试用例?
val oldList = listOf("a", "b", 0)
val newList = listOf("a", "c", 0, 1)
我们如何编写一个特定的测试用例,即我们的 onBindViewHolder
尚未针对新列表中的项目调用,这些项目相对于旧列表未修改,这基本上是 DiffUtil
所做的让一切顺利?
关于我们如何监听回收站视图本身的变化有什么想法吗?
我正在考虑使用 registerAdapterDataObserver(AdapterDataObserver)
,我们可以覆盖 onItemRangeChanged
并监听变化,但在测试中使用这种方法并不好。
DiffUtil 不会再次重新绑定任何相同的项目,因此根据要求,如果此测试用例 运行 使用 DiffUtil 以外的任何其他实现,它将失败。
@Test
fun testAdapter_noRebinding() {
val adapter = getAdapterObject()
lateinit var dataObserver: RecyclerView.AdapterDataObserver
var doesRebind = false
val oldList = listOf("a", "b", 0)
val newList = listOf("a", "b", 0)
ActivityScenario.launch(AdapterActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
dataObserver = object: RecyclerView.AdapterDataObserver(){
override fun onChanged() {
doesRebind = true
}
override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {
assertThat(positionStart).isEqualTo(0)
assertThat(itemCount).isEqualTo(3)
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {
doesRebind = true
}
override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {
doesRebind = true
}
}
adapter.registerAdapterDataObserver(dataObserver)
}
scenario.onActivity { activity ->
val liveData = getList(activity)
liveData.value = oldList
}
scenario.onActivity { activity ->
val liveData = getList(activity)
liveData.value = newList
}
scenario.onActivity { activity ->
assertThat(doesRebind).isEqualTo(false)
adapter.unregisterAdapterDataObserver(dataObserver)
}
}
}
更新
添加了使用 Mockito 的测试用例,但这些测试用例仅通过 robolectric 而不是 espresso
@Test
fun testBindableAdapter_incomingSameData_noRebindingShouldHappen() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
verify(fakeObserver, times(1)).onItemRangeInserted(0, 3)
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, never()).onChanged()
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_removeOneItem_verifyChangeOnlyOneItem() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeRemoved(0, 1)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_insertOneItem_verifyChangeOnlyOneItem() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeInserted(0, 1)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_moveOneItem_verifyNoRecreatingWholeList() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(INT_VALUE_1, STR_VALUE_0, STR_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeChanged(2, 1, null)
verify(fakeObserver, times(1)).onItemRangeChanged(0, 1, null)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
@Test
fun testBindableAdapter_updateOneItemContent_verifyOneItemUpdated() {
val adapter = createMultiViewTypeNoDataBindingBindableAdapter()
BindableAdapterTestFragmentPresenter.testBindableAdapter = adapter
val oldList = listOf(STR_VALUE_1, STR_VALUE_0, INT_VALUE_1).toMutableList()
val newList = listOf(STR_VALUE_1, STR_VALUE_1, INT_VALUE_1).toMutableList()
val fakeObserver = mock(RecyclerView.AdapterDataObserver::class.java)
ActivityScenario.launch(BindableAdapterTestActivity::class.java).use { scenario ->
scenario.onActivity { activity ->
adapter.registerAdapterDataObserver(fakeObserver)
val liveData = getRecyclerViewListLiveData(activity)
liveData.value = oldList
val liveDataNew = getRecyclerViewListLiveData(activity)
liveDataNew.value = newList
verify(fakeObserver, times(1)).onItemRangeChanged(1, 1, null)
adapter.unregisterAdapterDataObserver(fakeObserver)
}
}
}
浓缩咖啡出错
java.lang.RuntimeException: Wanted but not invoked:
adapterDataObserver.onItemRangeRemoved(0, 1);
-> at org.oppia.app.recyclerview.BindableAdapterTest$testBindableAdapter_removeOneItem_verifyChangeOnlyOneItem$$inlined$use$lambda.perform(BindableAdapterTest.kt:303)
Actually, there were zero interactions with this mock.
java.lang.RuntimeException: Wanted but not invoked:
adapterDataObserver.onItemRangeChanged(
2,
1,
null
);
-> at org.oppia.app.recyclerview.BindableAdapterTest$testBindableAdapter_moveOneItem_verifyNoRecreatingWholeList$$inlined$use$lambda.perform(BindableAdapterTest.kt:349)
Actually, there were zero interactions with this mock.