1. 准备工作
在之前的 Codelab 中,您已学过如何在 activity 之间导航。在此 Codelab 中,您将学习使用插桩测试对导航进行测试的一些不同方法。
前提条件
- 已在 Android Studio 中创建过测试目录。
- 已在 Android Studio 中编写过单元测试和插桩测试。
学习内容
- 如何使用插桩测试来测试 activity 或 fragment 之间的实际导航?
所需条件
- 一台安装了 Android Studio 的计算机。
- Words 应用的解决方案代码。
下载此 Codelab 的起始代码
在此 Codelab 中,您将在 Words 应用的解决方案代码中添加插桩测试。
如需获取此 Codelab 的代码并在 Android Studio 中打开它,请执行以下操作。
获取代码
- 点击提供的网址。此时,项目的 GitHub 页面会在浏览器中打开。
- 在项目的 GitHub 页面上,点击 Code 按钮,这时会出现一个对话框。
- 在对话框中,点击 Download ZIP 按钮,将项目保存到计算机上。等待下载完成。
- 在计算机上找到该文件(很可能在 Downloads 文件夹中)。
- 双击 ZIP 文件进行解压缩。系统将创建一个包含项目文件的新文件夹。
在 Android Studio 中打开项目
- 启动 Android Studio。
- 在 Welcome to Android Studio 窗口中,点击 Open an existing Android Studio project。
注意:如果 Android Studio 已经打开,请依次选择 File > New > Import Project 菜单选项。
- 在 Import Project 对话框中,前往解压缩的项目文件夹所在的位置(很可能在 Downloads 文件夹中)。
- 双击该项目文件夹。
- 等待 Android Studio 打开项目。
- 点击 Run 按钮 以构建并运行应用。请确保该应用按预期构建。
- 在 Project 工具窗口中浏览项目文件,了解应用的设置方式。
2. 起始应用概览
Words 应用包含一个主屏幕,该屏幕中显示了一个列表,其中的每个列表项都是字母表中的一个字母。点击某个字母会进入另一个屏幕,其中会显示以该字母开头的单词的列表。
3. 最佳实践
在 Kotlin 中,函数通常采用“驼峰命名法”,其中函数名称的第一个字母小写,后续各单词的首字母大写(例如 myCamelCaseFunction()
)。到目前为止,我们编写过的测试方法都是使用全小写字母并在单词间加下划线(例如 my_test_function()
)。这是因为,我们希望用详细的函数名称来清楚说明要测试的内容。这样,如果测试失败,名称本身就能清楚地表明哪方面失败了。在 Kotlin 中,如果用反引号将方法名称括起来,我们甚至可以在方法名称中使用空格,如下所示:``test function with spaces()
。请注意,反引号与单引号不同。您可以在带有波浪号 (~) 的按键上找到反引号。这样的命名方式有助于创建便于用户看懂且在测试失败时能够轻松辨别的函数名称。
不过,需要提醒的是,采用这种方式时,需要进行一些设置。请注意,此部分为可选学习内容。我们建议您阅读这些内容,但是不一定要采取“创建插桩测试目录”部分之前的步骤。
- 请注意,在 Android 中,只有以 API 级别 30 为目标平台时,函数名称中才可以包含空格。否则,您可能会遇到类似于以下内容的错误:
如需更改 API 级别,请前往 app/build.gradle,然后修改 minSdkVersion
和/或 targetSdkVersion
:
在本例中,targetSdkVersion
设为 30
,达到了所需级别。不过,minSdkVersion
为 19
。因此,为了在函数名称中使用空格,我们需要将其更改为 30
。这种做法有时未必可行,因此这是一项“锦上添花”的功能,而并非硬性要求。
- 即使以 API 级别 30 为目标平台,该方法也会带有红色下划线并显示以下消息:“Identifier is not supported in Android Projects”。
测试仍会执行并运行完毕。不过,如需去除下划线,请打开 Android Studio 设置/偏好设置,然后依次前往 Editor -> Inspections -> Kotlin Android -> Illegal Android Identifier -> Tests。
接下来,取消选中 Tests 复选框,并点击 Apply 或 OK。
现在,只要用反引号将方法名称括起来,方法就不会再带有红色下划线。
4. 创建测试目录
为 Words 应用创建插桩测试目录。
5. 创建插桩测试类
创建一个名为 NavigationTests.kt
的新类。
6. 编写导航测试
- 指定一个测试运行程序。
@RunWith(AndroidJUnit4::class)
- 然后,启动主 activity。
@get:Rule
val activity = ActivityScenarioRule(MainActivity::class.java)
- 现在,创建一个名为
navigate_to_word()
的方法。
@Test
fun navigate_to_word() {
}
- 在
navigate_to_word()
方法中,我们需要选择一个待选取的列表项。我们可以选择任意列表项,而且有多种选择方式。我们可以根据列表项在适配器中的位置来选择列表项,也可以根据列表项中包含的文本(即字母)来进行选择。请注意,如果您选择直接与RecyclerView
交互,就需要以下依赖项:
dependencies {
...
androidTestImplementation
‘com.android.support.test.espresso:espresso-contrib:3.0.2'
}
另一方面,如果您选择按文本选择列表项,可以使用您在之前的 Codelab 中用过的 withText()
方法。不过,务必要注意一点:使用这种方式时,如果文本未显示于屏幕上,测试将失败。稍后我们会对此进行介绍。
- 看看两种方式能否都尝试一下。请注意,我们的目标是在找到界面组件后点击该组件。
下面以点击字母“C”的列表项为例,展示了上述两种方式。
onView(withText("C")).perform(click())
onView(withId(R.id.recycler_view))
.perform(RecyclerViewActions
.actionOnItemAtPosition<RecyclerView.ViewHolder>(2, click()))
在此,我们要花点时间深入了解一下这两种方式。如果您运行其中任一方式并观察模拟器或设备,您会发现这两种方式都有效。使用 RecyclerViewActions
需要编写更多代码,但这种方式有一个明显的优势,那就是您能点击任何列表项而不必编写额外的代码。尝试第一种方式,但将字母“C”更改为“Z”,然后再次运行测试。您会发现测试失败并显示以下错误消息:androidx.test.espresso.NoMatchingViewException: No views in hierarchy found matching: with text: is "Z"。
这是因为“Z”位于列表末尾,在我们启动应用时位于屏幕外,必须滚动列表来让它显示在屏幕上。而 RecyclerViewAction
方式本身可以处理这种情况。尝试传递 25
作为位置值,然后观察模拟器或设备。
- 如果运行上述示例,您会发现我们可以成功启动下一个 activity。不过,我们需要使用断言来确认这一点。如果运行应用本身并点击任何给定列表项,您会看到应用栏中的标题显示“Words That Start With __”,其中的空格就是您点击过的那个字母。我们可以用这一点来检查导航功能是否工作正常。我们只需要断言会显示正确的字符串,后跟我们点击的字母。在之前的 Codelab 中,您曾写过类似的断言,试试看现在能否自己完成此断言!
onView(withText("Words That Start With C")).check(matches(isDisplayed()))
7. 概念性知识和最佳实践
测试中有两个非常重要的术语:“假正例”和“假负例”。
“假正例”是指即使测试出现问题而应该失败,仍会得出测试通过的结果。同理,“假负例”是指在测试没有问题而应该通过时,得出了测试失败的结果。
在前面编写的测试中,我们对要查找的字符串进行了硬编码。从代码角度来看,基于字符串资源构建字符串会更简洁,因为它在应用中完成。其工作方式与在普通应用代码中构建字符串略有不同,但仍然可以实现。但是,以这种方式构建字符串有可能会失败,因为从技术上讲,构建字符串属于业务逻辑。如果在测试中构建字符串失败,并且失败的方式与其在应用代码中失败的方式相同,就有可能产生假正例。这是因为两段代码都错误地构建了相同的字符串,因此测试中构建的错误文本与应用中显示的错误文本相符,故而产生了假正例。有鉴于此,最好将字符串硬编码为我们所需的值。
8. 解决方案代码
9. 恭喜
在此 Codelab 中,我们学习了以下内容:
- 了解如何为测试创建详细的函数名称。
- 了解如何测试 activity 或 fragment 的实际导航。
- 了解“假正例”和“假负例”。