我的片段UI中有一个listview,其元素集取决于来自viewmodel LiveData属性的值的状态。
我想为包含3个场景测试用例的片段创建与该属性的值集相关的工具测试,并且我不从哪里开始。
我的代码应如下所示:
class MyViewModel : ViewModel() {
var status = MutableLiveData("")
}
class MyFragment : Fragment() {
private lateinit var myViewModel: MyViewModel
private lateinit var myListView: ListView
override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
super.onAttach(context)
myViewModel =
ViewModelProviders.of(this, ViewModelProvider.Factory).get(MyViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
when (myViewModel?.status) {
"status1":
setListContent(items1)
"status1":
setListContent(items2)
"status1":
setListContent(items3)
else
setListContent
(items1)
}
}
private fun setListContent(itemsList: List<?>) {
myListView.adapter = MyCustomadapter(context!!, itemsList)
}
}
最佳答案
首先,您应该将针对片段本身的编写测试与针对 View 模型和实时数据的测试分开。
由于您想根据 View 模型实时数据编写片段测试,因此我认为一种解决方案是模拟 View 模型(或 View 模型所依赖的存储库)并使用FragmentScenario启动片段并对其进行测试。就像在codelab中所做的一样。
编辑:基于您提供的新代码
首先,我对您的代码进行了一些更改以使其可运行和可测试(此代码只是运行的代码,仅用于测试,并且不是格式正确且编写良好的代码):
MyFragment:
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ListView
import androidx.annotation.VisibleForTesting
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider.Factory
import androidx.lifecycle.ViewModelProviders
class MyFragment : Fragment() {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
lateinit var myViewModel: MyViewModel
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
lateinit var myListView: ListView
override fun onAttach(context: Context) {
super.onAttach(context)
val FACTORY = object : Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MyViewModel() as T
}
}
myViewModel =
ViewModelProviders.of(this, FACTORY).get(MyViewModel::class.java)
myListView = ListView(context)
myListView.adapter = MyCustomadapter(context, listOf("a", "b", "c"))
}
val items1 = listOf("a", "b", "c")
val items2 = listOf("1", "2")
val items3 = listOf("a1", "a2", "a3")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
when (myViewModel.status.value) {
"status1" ->
setListContent(items1)
"status2" ->
setListContent(items2)
"status3" ->
setListContent(items3)
else -> setListContent(items1)
}
return View(context)
}
private fun setListContent(itemsList: List<String>) {
myListView.adapter = MyCustomadapter(context!!, itemsList)
}
}
MyCustomadapter:
import android.content.Context
import android.database.DataSetObserver
import android.view.View
import android.view.ViewGroup
import android.widget.ListAdapter
class MyCustomadapter(private val context: Context, private val itemsList: List<String>) : ListAdapter {
override fun isEmpty(): Boolean {
return itemsList.isEmpty()
}
override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
return View(context)
}
override fun registerDataSetObserver(p0: DataSetObserver?) {
}
override fun getItemViewType(p0: Int): Int {
return 1
}
override fun getItem(p0: Int): Any {
return itemsList[p0]
}
override fun getViewTypeCount(): Int {
return 3
}
override fun isEnabled(p0: Int): Boolean {
return true
}
override fun getItemId(p0: Int): Long {
return 0
}
override fun hasStableIds(): Boolean {
return true
}
override fun areAllItemsEnabled(): Boolean {
return true
}
override fun unregisterDataSetObserver(p0: DataSetObserver?) {
}
override fun getCount(): Int {
return itemsList.size
}
}
MyViewModel:
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class MyViewModel : ViewModel() {
var status = MutableLiveData<String>()
}
在上面的代码中,我使用@VisibleForTesting批注来测试私有(private)字段。 [但是我建议不要这样做,而应使用公共(public)方法或UI组件来测试代码行为。由于您此处未提供任何UI组件,因此我没有其他简单的选择来测试您的代码]。
现在,我们将依赖项添加到应用程序模块的build.gradle中:
testImplementation 'junit:junit:4.12'
debugImplementation 'androidx.fragment:fragment-testing:1.1.0'
testImplementation 'androidx.test.ext:junit:1.1.1'
testImplementation 'org.robolectric:robolectric:4.3.1'
testImplementation 'androidx.arch.core:core-testing:2.1.0'
junit :用于纯单元测试['pure'表示您不能在junit测试中使用与android相关的代码]。我们总是需要这个库来编写我们的android测试。
片段测试:用于使用FragmentScenario。对于avoid robolectric style problem,我们使用“debugImplementation”代替“testImplementation”。
androidx.test.ext:junit :用于使用AndroidJUnit4测试运行程序。
robolectric :我们在这里使用robolectric在JVM上本地运行android工具测试(而不是在android仿真器或物理设备上运行)。
androidx.arch.core:core-testing :我们使用它来测试实时数据
为了能够在robolectric中使用android资源,我们需要在app build.gradle中添加一个测试选项:
android {
...
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
最后,我们编写一个简单的测试:
[将此测试放入“测试”源集中,而不是“androidTest”中。您也可以通过在android studio中按Ctrl + Shift + T或右键单击类名并按generate> Test ...,然后选择“测试”源集来为代码创建测试文件:
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.fragment.app.testing.launchFragment
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
@RunWith(AndroidJUnit4::class)
class MyFragmentTest {
@get:Rule
var instantExecutorRule = InstantTaskExecutorRule()
@Test
fun changingViewModelValue_ShouldSetListViewItems() {
val scenario = launchFragment<MyFragment>()
scenario.onFragment { fragment ->
fragment.myViewModel.status.value = "status1"
assert(fragment.myListView.adapter.getItem(0) == "a")
}
}
}
在上述测试中,我们通过设置实时数据值来测试设置列表 View 项。 “InstantTaskExecutorRule”用于确保以可预测的方式测试实时数据值(如here所述)。
如果要使用Espresso之类的库或其他库来测试UI组件(例如测试屏幕上显示的项目),请首先将其依赖项添加到gradle,然后按照here所述将
launchFragment<MyFragment>()
更改为launchFragmentInContainer<MyFragment>()
。