Android:如何根据viewmodel实时数据属性编写片段的单元测试? [英] Android : How to write a unit test for fragment depending on a viewmodel Live data attribute?

查看:133
本文介绍了Android:如何根据viewmodel实时数据属性编写片段的单元测试?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的片段UI中有一个listview,其元素集取决于来自viewmodel LiveData属性的值的状态.

I have a listview in my fragment UI that its elements set depend on status of a value that come from a viewmodel LiveData attribute.

我想为包含与该属性的值集相关的3个场景测试用例的片段创建工具测试,而我不从哪里开始.

I want to create instrumental test for the fragment which englobes 3 scenarios test case related to the value set of that attribute and I don't where to start.

我的代码应如下所示:

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)
}

}

推荐答案

首先,您应该将针对片段本身的编写测试与针对视图模型和实时数据的测试分开.

First you should separate writing tests for fragment itself and tests for view model and live data.

由于您要根据视图模型实时数据为片段编写测试,因此我认为一种解决方案是模拟视图模型(或视图模型所依赖的存储库)并启动您的片段使用FragmentScenario并对其进行测试.就像在 codelab中所做的一样.

Since you want to write test for fragment depending on a viewmodel Live data, then I think a solution is to mock the view model (or the repository that view model depends on) and launch your fragment using FragmentScenario and test it. Like what is done in this codelab.

修改:基于您提供的新代码

首先,我对您的代码进行了一些更改以使其可运行和可测试(此代码只是运行的代码,仅用于测试,并且不是格式正确且编写良好的代码):

First I do some changes in your code to make it runnable and testable (This code is just a code that runs and is just for testing and isn't a well-formed and well-written code):

MyFragment:

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:

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:

MyViewModel:

import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel

class MyViewModel : ViewModel() {
    var status = MutableLiveData<String>()
}

在上面的代码中,我使用@VisibleForTesting批注来测试私有字段. [但是我建议不要这样做,而应使用公共方法或UI组件来测试代码行为.由于您此处未提供任何UI组件,因此我没有其他简单的选择来测试您的代码].

In the above code, I used the @VisibleForTesting annotation for able to testing private fields. [But I recommend to not do like this, and instead, use public methods or the UI components to test the code behaviour. Since you have not provided any UI component here, I have no other simple choice for testing your code].

现在,我们将依赖项添加到应用程序模块的build.gradle:

Now we add dependencies to app modules' 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测试.

junit: is for pure unit testing ['pure' means that you can't use android related code in your junit tests]. We always need this library for writing our android tests.

片段测试 :用于使用避免机器人风格的问题,我们使用'debugImplementation'而不是'testImplementation'.

fragment-testing: for using FragmentScenario. For avoid robolectric style problem we use 'debugImplementation' instead of 'testImplementation'.

androidx.test.ext:junit :用于使用AndroidJUnit4测试运行程序.

androidx.test.ext:junit: is for using AndroidJUnit4 test runner.

robolectric :我们在这里使用robolectric在JVM上本地运行android工具测试(而不是在android仿真器或物理设备上运行).

robolectric: we use robolectric here for running android instrumentation tests on JVM - locally (instead of running on android emulator or physical device).

androidx.arch.core:core-testing :我们用它来测试实时数据

androidx.arch.core:core-testing: we use this for testing live data

为了能够在robolectric中使用android资源,我们需要在app build.gradle中添加一个测试选项:

For able to use android resources in robolectric we need to add a test option to app build.gradle:

android {
    ...
    testOptions {
        unitTests {
            includeAndroidResources = true
        }
    }
}

最后,我们编写一个简单的测试:

[将此测试放入测试"源集中,而不是在"androidTest"中.您也可以通过在android studio中按Ctrl + Shift + T或右键单击类名并按generate> Test ...,然后选择"test"源集],为代码创建测试文件:

[put this test in "test" source set and not in "androidTest". Also you can create test file for your code by pressing Ctrl + Shift + T in android studio, or by right clicking on class name and pressing generate>Test... and selecting 'test' source set]:

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")
        }
    }
}

在上述测试中,我们通过设置实时数据值测试了设置列表视图项. "InstantTaskExecutorRule"用于确保以可预测的方式测试实时数据值(如

In the above test, we tested setting list view items by setting live data value. The 'InstantTaskExecutorRule' is for assuring that live data value will be tested in predictable way (As explained here).

如果要使用Espresso之类的库或其他库来测试UI组件(例如测试屏幕上显示的项目),请首先将其依赖项添加到gradle,然后按照

If you want to test your UI components (like testing displayed items in screen) with libraries like Espresso or other libraries first add its dependency to gradle and then change the launchFragment<MyFragment>() to launchFragmentInContainer<MyFragment>() as described here.

这篇关于Android:如何根据viewmodel实时数据属性编写片段的单元测试?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆