Android:如何根据视图模型实时数据属性为片段编写单元测试?

2024-05-11

我的片段 UI 中有一个列表视图,其元素集取决于来自视图模型 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)
}

}


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

因为你想编写测试取决于视图模型的片段实时数据,那么我认为解决方案是模拟视图模型(或视图模型依赖的存储库)并使用 FragmentScenario 启动片段并测试它。就像在这个中所做的那样codelab https://codelabs.developers.google.com/codelabs/advanced-android-kotlin-training-testing-test-doubles/#0.

编辑:根据您新提供的代码

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

我的片段:

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

我的自定义适配器:


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
    }

}

我的视图模型:

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

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

在上面的代码中,我使用 @VisibleForTesting 注释来测试私有字段。 [但我建议不要这样做,而是使用公共方法或 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:用于纯单元测试[“纯”意味着您不能在junit测试中使用android相关代码]。我们总是需要这个库来编写我们的 Android 测试。

片段测试:用于使用片段场景 https://developer.android.com/training/basics/fragments/testing. For 避免机器人电动风格问题 https://stackoverflow.com/questions/54007609/robolectric-androidx-fragments-noclassdeffounderror我们使用“debugImplementation”而不是“testImplementation”。

androidx.test.ext:junit:用于使用 AndroidJUnit4 测试运行器。

机器人电动:我们在这里使用 robolectric 在 JVM 上本地运行 Android 仪器测试(而不是在 Android 模拟器或物理设备上运行)。

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

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

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

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

[将此测试放在“test”源集中而不是“androidTest”中。您还可以通过在 android studio 中按 Ctrl + Shift + T 或右键单击类名并按“生成”>“测试...”并选择“测试”源集来为代码创建测试文件]:

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”用于确保以可预测的方式测试实时数据值(如所解释的here https://codelabs.developers.google.com/codelabs/advanced-android-kotlin-training-testing-basics/index.html?index=..%2F..index#8).

如果您想使用 Espresso 或其他库等库测试您的 UI 组件(例如测试屏幕中显示的项目),请首先将其依赖项添加到 gradle,然后更改launchFragment<MyFragment>() to launchFragmentInContainer<MyFragment>()如上所述here https://developer.android.com/training/basics/fragments/testing#create.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android:如何根据视图模型实时数据属性为片段编写单元测试? 的相关文章

  • 如何改变android中menuItem的背景颜色?

    我正在以编程方式将菜单项添加到菜单中 我想在选择特定项目时添加背景颜色 如何为 menuItem 添加背景 您的回答将不胜感激 虽然其他答案提供了更改样式 这会影响all菜单项 据我了解 需要更改一个菜单项 我建议你使用android ac
  • 在 Android 中的活动、服务和应用程序之间传递变量

    有人可以给我提供以下活动 服务 应用程序组合的示例吗 我拥有这三个 但我已经把我的应用程序弄得一团糟 试图在这个地方传递一堆变量 现在我不知道发生了什么 请注意 我是 Android 新手 最近我一直在努力解决这个问题 因为有很多方法可以实
  • XCode:代码覆盖率数据生成失败

    当我尝试运行单元测试时 我在 Objective C 项目中遇到了这个可怕的问题 我已经尝试了以下所有方法 根据各种 SOF 帖子 清除派生数据 删除 Library 中的所有模拟器设置 重新启动 XCode 重新启动MBP 多次重置模拟器
  • 无法合并 Dex - Android Studio 3.0

    当我在稳定频道中将 Android Studio 更新到 3 0 并运行该项目时 我开始收到以下错误 Error Execution failed for task app transformDexArchiveWithExternalLi
  • 将标题设置为操作栏时出现空指针异常

    Error Caused by java lang NullPointerException Attempt to invoke virtual method void android app ActionBar setTitle java
  • Google Inbox 类似 RecyclerView 项目打开动画

    目前 我正在尝试实现 Google Inbox 例如RecyclerView行为 我对电子邮件打开动画很好奇 我的问题是 该怎么做 我的意思是 他们使用了哪种方法 他们用过吗ItemAnimator dispatchChangeStarti
  • 如何在照片删除后刷新 Android 的 MediaStore

    问题 如何使媒体存储刷新其已删除文件的条目 从外部存储中删除代码中的照片后 我仍然在图库中看到已删除照片的插槽 空白照片 画廊似乎反映了媒体存储 并且在媒体存储中找到了已删除的照片 直到手机重新启动或通常 直到重新扫描媒体为止 尝试扫描已删
  • 所有junit测试后的清理

    在我的项目中 我必须在所有测试之前进行一些存储库设置 这是使用一些棘手的静态规则来完成的 然而 在所有测试之后我不知道如何进行清理 我不想保留一些神奇的静态数字来引用所有测试方法的数量 我应该一直维护它 最受赞赏的方法是添加一些侦听器 该侦
  • 如何将单选按钮状态保存到已保存/共享首选项?

    我可以在保存的首选项中保存字符串 但很难保存单选按钮 public class PersonalDetailsf extends Activity private SharedPreferences sharedPreferences pr
  • 如何检测 Google Play 上是否有我的应用程序的更新? [复制]

    这个问题在这里已经有答案了 有没有办法以编程方式检查 Google Play 上我的应用程序是否有更新 以便通知用户 我知道 android google play 有自动通知 但我想使用我自己的通知 弹出消息来更新可用性 有点像 Vibe
  • Locale.getDefault().getCountry() 返回空字符串

    我正在尝试使用国家 地区代码获取用户语言 例如en US es es 但是当我使用Locale getDefault getCountry 它返回空字符串 虽然它给了我正确的语言Locale getDefault getLanguage N
  • 在 Android 中使用 iText 读取或打开 PDF 文件

    我是 Android 应用程序开发新手 使用 iText 我完成了 PDF 创建并在创建的文件上写入 现在我想阅读该 PDF 文件 如何使用 iText 打开或阅读 PDF 文件 例子将是可观的 那么提前 哪个是渲染 PDF 文件的最佳库
  • Facebook LoginActivity 未正确显示

    我有一个使用 Facebook 登录的应用程序 我有 FacebookSDK 并且使用 com facebook LoginActivity 问题是 在 10 英寸平板电脑上 当显示软键盘时 活动无法正确显示 我使用的是 Samsung G
  • 如何从SurfaceView绘制到Canvas?

    我正在尝试做简单的画家 问题是Android看起来有三个独立的Canvas并给我它来顺序绘制 我用以下方式制作了用户界面SurfaceView 把霍尔德从中拿走 Override protected void onCreate Bundle
  • Android开发:未定义方法

    大家好 我是 Android 和 Eclipse 的新手 我刚刚遵循了developer android com 上的教程 现在我在添加操作栏 http developer android com training basics actio
  • 如何在 WebView 中添加 JavaScript 函数并稍后在提交 reCAPTCHA 时从 HTML 调用它

    我在 WebView 中添加一个 JavaScript 函数 如下所示 Kotlin val webView findViewById R id webview as WebView webView getSettings setJavaS
  • Jetpack Compose 部分或开放侧边框

    我正在尝试绘制部分或一侧开放的矩形圆形边框以实现此效果 玩了一下之后我得到了这个 这是通过以下方式完成的 RoundedCornerShape topStartPercent 50 bottomStartPercent 50 start R
  • gradle-experimental:0.1.0 buildConfigField

    谁知道怎么定义buildConfigField在实验性的 gradle 插件中 android productFlavors create demo applicationId com anthonymandra rawdroid buil
  • Amazon IAP 不会调用 onPurchaseResponse

    我有一个 Android 应用程序 它使用 IAP 我正在发送PurchasingManager initiateGetUserIdRequest 并得到用户识别成功 in onGetUserIdResponse 得到回复后Purchasi
  • 使用支持库中的 BottomSheet 时如何调暗背景?

    怎样才能让背景像显示的那样变暗here https material design storage googleapis com publish material v 8 material ext publish 0Bzhp5Z4wHba3

随机推荐

  • 无法弄清楚为什么 Bison 抛出“由于冲突,规则在解析器中无用”

    我正在为一种非常简单的编程语言编写 BNF 语法 并使用 Flex 和 Bison 进行编译 我只有 3 种变量和常量类型 实数 整数 字符串 我的 l 文件具有 ID 的标记定义 如下所示 DIGIT 0 9 LETTER a zA Z
  • 有没有办法将 Excel 单元格条目转换为一致的日期和时间格式?

    我正在处理雨量计数据记录器生成的 csv 文件中的一些雨量计数据 我发现日期和时间的记录不一致 以以下两种格式之一交替显示 Format 1 mm dd yyyy hh mm 24 hour clock or Format 2 mm dd
  • WordPress 访问

    我正在与朋友一起开发一个网站 使用Wordpress我们正在尝试从我的计算机和他的计算机访问同一个 WordPress 帐户 以便我们可以一起在网站上工作 我们尝试将彼此添加为管理员 但只能从创建管理员的计算机上访问新帐户 有谁知道如何做到
  • Android布局中如何实现Button的自动宽度

    我有几个垂直列出的按钮 我需要它们都具有相同的宽度 而且还要显示其中的所有文本 基本上我需要所有它们的宽度作为最大的宽度的包裹宽度 希望我解释得很好 现在 我已经有一种布局可以在我的 Samsung Galaxy S2 4 1 2 上运行
  • 基于 json 文件动态显示选择、复选框、日期选择器等

    对我之前的问题的补充 我根据 json 文件动态显示输入字段 现在我想根据它们的组显示选择项 复选框和日期选择器 我如何解决这个问题 我需要将这些元素推入computeJSON 但写入例如选择options item selection不管
  • 检测 SVGAnimatedString 的类名

    我在构建 SVG 地图时遇到问题 触发的功能 g 上的 onmouseover 不起作用 我当时用的 window onmouseover function e console log e target className 查看类名是否有问
  • 如何在 Java 中用 \n 替换 \\n

    我有一个string test first n middle n last 现在我想更换所有 n by n 我试过了test replaceAll n n and test replaceAll n n 但它们不起作用 有人有解决办法吗 T
  • 如果使用 unicode 字符,则从数据库中进行 SELECT 会出现问题

    我在用着latest带DatabaseLibrary的python和机器人框架版本 https franz see github io Robotframework Database Library api 1 0 1 DatabaseLi
  • 正则表达式没有按预期工作?

    我有这个正则表达式 new RegExp a z 0 9 ig 我正在测试一个不应该工作的字符串 vc 但它确实通过了测试 而且它不应该 new RegExp a z 0 9 ig test vc true 但如果我删除其中一个 or or
  • static_cast 和对指针的引用

    谁能告诉我为什么这不能编译 struct A struct B public A int main B b A a b B b1 static cast
  • 如何从 Ruby 2.0 中的字符串数组获取值

    我有这个字符串数组 array nike air nike steam nike softy nike strength smooth sleeper adidas air addidas jogar adidas softy adidas
  • 我无法从 Android 模拟器中删除日语 IME

    我已经多次看到这个问题 但答案总是 从 语言和键盘设置 菜单中取消选中 IME 问题是那里没有复选框 选择菜单 自定义区域设置 en US 菜单 设置 语言和键盘 选择语言 选择 英语 美国 菜单 设置 语言和键盘 日语输入法是唯一的输入法
  • opencv形态扩张滤波器作为最大滤波器

    就像中值滤波器的定义一样 我可以将 最大滤波器 定义为局部窗口 例如dst x y max 3x3 局部窗口像素 但我在opencv中找不到这样的过滤器 最接近的是 dilate 函数 然后我使用 dilate 函数的默认配置 但结果不正确
  • crashlyticsGenerateSymbolsRelease - 超出 GC 开销限制

    大约一周前 我们已成功将 crashlytics 集成到我们的 cocos2d x C 项目 ndk 14b 中 一切看起来都很好 但大约几天前任务 crashlyticsUploadSymbolsRelease 开始抛出此错误 Execu
  • 如何将js文件从同一文件夹导入chrome扩展中的background.js

    我在导入与 background js 脚本库 位于同一库中的文件 score js 时遇到问题 我对 js 和 chrome 扩展都很陌生 我研究了 require js 并做了这个 背景 html h1 Tab Manager h1
  • 显示葡萄牙语字符 Android

    我正在开发一个 Android 应用程序 当它应该写 n o 或 cabe a 等单词 例如带有 或 的字符 时 它会写 或 我已经检查过源文件编码 它是UTF 8 看起来 UTF8 被解释为 ISO 8859 x 理论上 你可以提供 ja
  • 如何制作复选按钮? (带有标签的隐藏复选框作为按钮:仅限 CSS)

    Using 方法1 创建可点击标签 https stackoverflow com a 6293626 1326147 用 CSS 隐藏复选框 https stackoverflow com a 18078908 1326147 and 使
  • 如何从字符串转换为数组?

    If s 1 2 3 4 5 我们如何从中获得一个整数数组 我想返回 5 个元素Array Int64 1 1 2 3 4 5 正如 isebarn 使用的那样 split s 对于将字符串拆分为单词非常有用 默认情况下按空格拆分 juli
  • 如何隐藏 URL 中的 ID

    我以前在 Stack Overflow 上见过这类问题 但没有一个真正有帮助 我也用谷歌搜索过 但没有骰子 我想知道如果用户单击选项卡本身是否可以隐藏 URL 中的 ID 这是网页 www planet nu dev new experia
  • Android:如何根据视图模型实时数据属性为片段编写单元测试?

    我的片段 UI 中有一个列表视图 其元素集取决于来自视图模型 LiveData 属性的值的状态 我想为片段创建工具测试 该片段包含与该属性的值集相关的 3 个场景测试用例 但我不知道从哪里开始 我的代码应该如下所示 class MyView