测试列表和适配器

1. 准备工作

在之前的 Codelab 中,您已学过如何编写和运行单元测试和插桩测试。此 Codelab 将介绍编写测试的一些最佳做法,以及如何添加特定的 Gradle 依赖项以进行测试。此外,您还可以练习编写更多单元测试和插桩测试。

前提条件

  • 已在 Android Studio 中打开一个现有项目。
  • 已在 Android Studio 中编写过单元测试和插桩测试。
  • 具有一定的在 Android Studio 中浏览项目的经验。
  • 具有一定的在 Android Studio 中处理 build.gradle 文件的经验。

学习内容

  • 编写测试的基础知识。
  • 如何添加专用于测试的 Gradle 依赖项?
  • 如何使用插桩测试来测试列表?

所需条件

  • 一台安装了 Android Studio 的计算机。
  • Affirmations 应用的解决方案代码。

下载此 Codelab 的起始代码

在此 Codelab 中,您将在 Affirmations 应用的解决方案代码中添加插桩测试。

  1. 进入为此项目提供的 GitHub 代码库页面。
  2. 验证分支名称是否与此 Codelab 中指定的分支名称一致。例如,在以下屏幕截图中,分支名称为 main

1e4c0d2c081a8fd2.png

  1. 在项目的 GitHub 页面上,点击 Code 按钮,以打开一个弹出式窗口。

1debcf330fd04c7b.png

  1. 在弹出式窗口中,点击 Download ZIP 按钮,将项目保存到计算机上。等待下载完成。
  2. 在计算机上找到该文件(可能在 Downloads 文件夹中)。
  3. 双击 ZIP 文件进行解压缩。系统将创建一个包含项目文件的新文件夹。

在 Android Studio 中打开项目

  1. 启动 Android Studio。
  2. Welcome to Android Studio 窗口中,点击 Open

d8e9dbdeafe9038a.png

注意:如果 Android Studio 已经打开,则改为依次选择 File > Open 菜单选项。

8d1fda7396afe8e5.png

  1. 在文件浏览器中,转到解压缩的项目文件夹所在的位置(很可能在 Downloads 文件夹中)。
  2. 双击该项目文件夹。
  3. 等待 Android Studio 打开项目。
  4. 点击 Run 按钮 8de56cba7583251f.png 以构建并运行应用。请确保该应用按预期构建。

2. 起始应用概览

Affirmations 应用包含一个屏幕,该屏幕向用户显示了一系列与自我肯定话语字词配对的图片。

3. 最佳做法

根据设计,测试代码看起来与应用的业务逻辑不同。这是因为测试本不应包含逻辑;它们只是为了测试逻辑而已。因此,测试不应包含条件语句(如 ifwhen),也不应包含控制流语句(如 forwhile),同样也不应操纵值或进行任何实际计算。

测试有时可能需要使用其中某些代码,但一般情况下您应避免使用。由于我们要在应用中测试此类逻辑,因此,如果我们在测试中包含这类代码,测试可能会失败,具体原因与应用代码运行失败的原因相同。

我们的单元测试应该只从我们的应用中调用测试所必需的代码,并测试因调用这些代码所生成的代码的值或状态。界面测试应只针对界面的预期状态进行测试。您可能一时半会儿还理解不了这个概念,不过没有关系!之后的 Codelab 中会介绍一些主题,帮助您理解此概念。与此同时,我们会编写更多测试,因此请特别注意我们在编写测试时采用的方法。

4. 创建测试目录

上一个 Codelab 中,您学习了如何为插桩测试创建 androidTest 目录。对于此项目,请对 androidTest 目录和 test 目录重复执行这个过程。这两个目录对应的过程相同,唯一的区别是:对于 test 目录,您必须从 New Directory 下拉菜单中选择 test/java,而非 androidTest/java。为每个名为 com.example.affirmations 的新目录创建一个新软件包。

d762ecd8950e97b2.png

5. 创建插桩测试类

androidTest -> com.example.affirmations 中创建一个名为 AffirmationsListTests.kt 的新类。

Dice Roller 应用一样,Affirmations 只有一个 activity。为了测试该 activity 的界面,我们必须明确说明需要启动它。看看您能否回想起自己是如何做到这一点的!

  1. 将测试运行程序添加到新创建的类中。
@RunWith(AndroidJUnit4::class)
  1. 为主 activity 创建 activity 场景规则。
@get:Rule
val activity = ActivityScenarioRule(MainActivity::class.java)
  1. Affirmations 应用会显示一系列图片及其对应的积极自我肯定话语。界面不允许使用者与相关项进行任何互动(例如点击或滑动)。因此,对于此应用,插桩测试仅会测试静态数据。创建一个名为 scroll_to_item() 的测试方法。请注意,它必须带有 @Test 注解。

此测试应滚动到列表中包含的特定项。我们尚未介绍过这种办法,因为它需要一个我们的项目尚未引用过的方法。在继续测试之前,我们需要添加一些测试依赖项。

6. 添加插桩测试依赖项

您应该已经熟悉如何添加 Gradle 依赖项以便在应用代码中使用。Gradle 还可让我们专为单元测试和插桩测试添加依赖项。打开位于 app -> build.gradle 的应用级 build.gradle 文件。在依赖项部分中,有三种类型的依赖项实现:implementationtestImplementationandroidTestImplementation

implementation 适用于将在应用本身中使用的依赖项,testImplementation 适用于单元测试中使用的依赖项,androidTestImplementation 适用于插桩测试中使用的依赖项。

  1. 添加一个依赖项以允许在插桩测试中与 RecyclerView 互动。将以下库添加为 androidTestImplementation
androidx.test.espresso:espresso-contrib:3.4.0

依赖项如下所示:

dependencies {
    ...
    androidTestImplementation
'androidx.test.espresso:espresso-contrib:3.4.0'
}
  1. 现在同步项目。

7. 测试 RecyclerView

  1. 项目同步完成后,返回 AffirmationsListTests.kt 文件。提供 ViewInteraction 以便对 onView() 执行操作。onView() 方法需要传入 ViewMatcher。使用 withId(),同时确保传入用于自我肯定话语的 RecyclerView 的 ID。现在,对 ViewInteraction 调用 perform()。这正是新添加的依赖项在发挥作用!现在可以传入 RecyclerViewActions.scrollToPosition<RecyclerView.Viewholder>(9) ViewAction
onView(withId(R.id.recycler_view)).perform(
   RecyclerViewActions
       .scrollToPosition<RecyclerView.ViewHolder>(9)
)

理解此行的语法不是很关键,但值得探索。顾名思义,RecyclerViewActions 是一个可让您的测试对 RecyclerView 执行操作的类。scrollToPosition()RecyclerViewActions 类中的静态方法,作用是滚动到指定位置。此方法会返回一个所谓的泛型。泛型不在此 Codelab 的讨论范围内,但在本例中,您可以将其视为 scrollToPosition() 方法,该方法会返回 RecyclerView 中的任何项(可以是任何内容)。

在我们的应用中,RecyclerView 中的项是 ViewHolder,因此我们在方法调用后放置一对尖括号,并在其中指定 RecyclerView.ViewHolder。最后,传入列表的最终位置 (9)。

  1. 现在,已经能够滚动到 RecyclerView 的所需位置,请断言以确保界面会显示预期的信息。确保在您滚动到最后一项内容后,系统会显示与最终自我肯定话语相关的文本。以 ViewInteraction 开头,但这次传入新的 ViewMatcher(本例中为 withText())。对于该方法,请传递包含最终自我肯定话语文本的字符串资源。withText() 方法会根据界面组件显示的文本标识该组件。对于该组件,您只需检查它是否显示了所需的文本。这可以通过对 ViewInteraction 调用 check() 来实现。check() 需要 ViewAssertion,您可以为后者使用 matches() 方法。最后,通过传递 isDisplayed() 方法断言会显示界面组件。
onView(withText(R.string.affirmation10))
    .check(matches(isDisplayed()))

回到关于对要滚动到的位置进行硬编码的备注,我们可以使用 RecyclerViewActions 来克服此问题。如果您不确定列表的长度,可以使用 scrollTo 操作。scrollTo 函数需要 Matcher<View!>! 才能查找特定项。这可能会涉及很多方面,对于此测试,请使用 withText。将其应用于您刚才编写的测试,具体代码将如下所示:

onView(withId(R.id.recycler_view)).perform(
   RecyclerViewActions
       .scrollTo<RecyclerView.ViewHolder>(
           withText(R.string.affirmation10)
       )
)

onView(withText(R.string.affirmation10))
    .check(matches(isDisplayed())
)

现在,一切准备就绪,可以运行测试了。您应该会看到设备或模拟器滚动到列表底部,然后测试会通过。如果要确保测试结果准确无误,请将字符串 ID 替换为 R.string.affirmation1。滚动后,此字符串资源不会显示,测试应该会失败。

RecyclerViewActions 类中提供了许多方法,建议您了解一下可用的方法

8. 创建本地测试类

test -> com.example.affirmations 中创建一个名为 AffirmationsAdapterTests.kt 的新类。

9. 添加本地测试依赖项

  1. 在此 Codelab 的前面部分,我们讨论了三种不同类型的依赖项实现,并且您添加了一个进行插桩测试所需的依赖项。现在,您要添加一个进行本地测试所需的依赖项。依次转到 app -> build.gradle,然后将以下代码添加为单元测试依赖项:
org.mockito:mockito-core:3.12.4

依赖项应如下所示:

dependencies {
    ...
    testImplementation 'org.mockito:mockito-core:3.12.4'
}
  1. 现在同步项目。

10. 测试适配器

此特定应用不适用于单元测试,因为没有太多逻辑可供测试。不过,我们可以再获得一些测试各种组件的经验,为日后的测试做好准备。

  1. 在单元测试类中添加以下行:
private val context = mock(Context::class.java)

mock() 方法来自我们刚在项目中实现的库。模拟是单元测试不可或缺的一部分,但并不在此 Codelab 的讨论范围内。我们将在另一个 Codelab 中详细介绍模拟。在 Android 中,Context 是应用当前状态的上下文,但请注意,单元测试在 JVM 上运行,而不是在实际设备上运行,因此没有 Context。借助模拟方法,我们可以创建 Context 的“模拟”实例。它没有任何实际功能,但可用于测试需要上下文的方法。

  1. 创建一个名为 adapter_size() 的函数,并添加注解将其标记为测试。此测试的目的是确保适配器的大小就是传递给适配器的列表的大小。为此,请创建 ItemAdapter 的实例,并传入 Datasource 类中的 loadAffirmations() 方法返回的列表。您也可以创建一个新的列表并进行测试。对于单元测试,最佳做法是创建自己的测试专用数据,因此我们将为此测试创建自定义列表。
val data = listOf(
   Affirmation(R.string.affirmation1, R.drawable.image1),
   Affirmation(R.string.affirmation2, R.drawable.image2)
)
  1. 现在,创建 ItemAdapter 的实例,并传入在前面步骤中创建的 contextdata 变量。
val adapter = ItemAdapter(context, data)

Recycler 视图适配器提供了一种方法,该方法会返回名为 getItemCount() 的适配器的大小。对于此应用,该方法如下所示:

/**
* Return the size of your dataset (invoked by the layout manager)
*/
override fun getItemCount() = dataset.size
  1. 这是应该测试的方法。确保此方法返回的值与您在第 2 步中创建的列表大小一致。使用 assertEquals() 方法,并比较列表大小和适配器大小的值。
assertEquals("ItemAdapter is not the correct size", data.size, adapter.itemCount)

您已经很熟悉 assertEquals() 方法,不过有必要详细检查命令行。第一个参数是一个字符串,如果测试失败,该字符串会显示在测试结果中。第二个参数是预期值。第三个参数是实际值。您的测试类应如下所示:

f81a27f5c1cf055e.png

  1. 现在,运行测试!

11. 解决方案代码

12. 恭喜

在此 Codelab 中,您:

  • 学习了如何添加测试专用依赖项。
  • 学习了如何使用插桩测试与 RecyclerView 互动。
  • 讨论了一些基本的测试最佳做法。