Android: Как написать модульный тест для фрагмента в зависимости от атрибута данных реального времени модели представления? - PullRequest
0 голосов
/ 09 января 2020

У меня есть просмотр списка в пользовательском интерфейсе фрагмента, набор элементов которого зависит от состояния значения, полученного из атрибута LiveData viewmodel.

Я хочу создать инструментальный тест для фрагмента, включающего 3 сценария ios контрольный пример, связанный с набором значений этого атрибута, и я не знаю, с чего начать.

Мой код должен выглядеть примерно так:

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

}

1 Ответ

2 голосов
/ 09 января 2020

Сначала вы должны разделить тесты записи для самого фрагмента и тесты для модели представления и живых данных.

Поскольку вы хотите написать тест для фрагмента в зависимости от модели представления Живые данные , затем я думаю, что решение состоит в том, чтобы смоделировать модель представления (или хранилище, от которого зависит модель представления) и запустить ваш фрагмент с использованием FragmentScenario и протестировать его. Как то, что сделано в этой кодовой метке .

Редактировать: на основе вашего нового предоставленного кода

Сначала я внесу некоторые изменения в ваш код, чтобы сделать он может работать и тестироваться (этот код является просто кодом, который выполняется и предназначен только для тестирования, а не является правильно сформированным и хорошо написанным кодом):

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 для возможности тестирования приватных полей. [Но я рекомендую не делать этого, а вместо этого использовать методы publi c или компоненты пользовательского интерфейса для проверки поведения кода. Поскольку вы не предоставили здесь никакого компонента пользовательского интерфейса, у меня нет другого простого выбора для тестирования вашего кода].

Теперь мы добавляем зависимости в 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' означает, что вы не можете использовать android связанный код в своих тестах junit]. Нам всегда нужна эта библиотека для написания наших android тестов.

фрагментное тестирование : для использования FragmentScenario . Для во избежание проблемы стиля robolectri c мы используем 'debugImplementation' вместо 'testImplementation'.

androidx.test.ext: junit : для использования AndroidJUnit4.

robolectri c: мы используем robolectri c здесь для запуска android инструментальных тестов на JVM - локально ( вместо запуска на android эмуляторе или физическом устройстве).

androidx.arch.core: core-testing : мы используем это для тестирования живых данных

Для возможности использования android ресурсов в robolectri c нам нужно добавить опцию теста в приложение build.gradle:

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

И, наконец, мы пишем простой тест :

[поместите этот тест в исходный набор тестов, а не в androidTest. Также вы можете создать тестовый файл для своего кода, нажав Ctrl + Shift + T в android studio, или щелкнув правой кнопкой мыши на имени класса и нажав generate> Test ... и выбрав 'test' source 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 предназначен для гарантии того, что значение данных в реальном времени будет проверено предсказуемым образом (как объяснено здесь ).

Source set structure

Если вы хотите протестировать свои компоненты пользовательского интерфейса (например, тестировать отображаемые элементы на экране) с такими библиотеками, как Espresso или другие, сначала добавьте их зависимость в gradle, а затем измените launchFragment<MyFragment>() на launchFragmentInContainer<MyFragment>(), как описано здесь .

...