1. 准备工作
在之前的 Codelab 中,您已学过如何使用 ViewModel
处理业务逻辑,以及如何使用 LiveData
构建响应式界面。在此 Codelab 中,您将学习如何编写单元测试来检查 ViewModel
代码能否正常运行。
前提条件
- 已在 Android Studio 中创建过测试目录。
- 已在 Android Studio 中编写过单元测试和插桩测试。
- 已在 Android 项目中添加过 Gradle 依赖项。
学习内容
- 如何为
ViewModel
和LiveData
编写单元测试?
所需条件
- 一台安装了 Android Studio 的计算机。
- Cupcake 应用的解决方案代码。
下载此 Codelab 的起始代码
在此 Codelab 中,您将基于之前的解决方案代码在 Cupcake 应用中添加插桩测试。
如需获取此 Codelab 的代码并在 Android Studio 中打开它,请执行以下操作。
获取代码
- 点击提供的网址。此时,项目的 GitHub 页面会在浏览器中打开。
- 检查并确认分支名称与 Codelab 中指定的分支名称匹配。例如,在以下屏幕截图中,分支名称为 main。
- 在项目的 GitHub 页面上,点击 Code 按钮,以打开一个弹出式窗口。
- 在弹出式窗口中,点击 Download ZIP 按钮,将项目保存到计算机上。等待下载完成。
- 在计算机上找到该文件(很可能在 Downloads 文件夹中)。
- 双击 ZIP 文件进行解压缩。系统将创建一个包含项目文件的新文件夹。
在 Android Studio 中打开项目
- 启动 Android Studio。
- 在 Welcome to Android Studio 窗口中,点击 Open。
注意:如果 Android Studio 已经打开,则改为依次选择 File > Open 菜单选项。
- 在文件浏览器中,转到解压缩的项目文件夹所在的位置(很可能在 Downloads 文件夹中)。
- 双击该项目文件夹。
- 等待 Android Studio 打开项目。
- 点击 Run 按钮 以构建并运行应用。请确保该应用按预期构建。
2. 起始应用概览
Cupcake 应用包含一个主屏幕,主屏幕中显示一个订单屏幕,里面包含三个用于选择纸杯蛋糕数量的选项。点击某个选项后,您会转到另一个屏幕,您可在该屏幕中选择口味,然后再转到一个屏幕为订单选择取货日期。选择好之后,您可以将订单发送到另一个应用。在上述过程中,您可以在任何一个阶段取消订单。
3. 创建单元测试目录
按照您在之前的 Codelab 中执行的操作步骤,为 Cupcake 应用创建一个单元测试目录。
4. 创建单元测试类
创建一个名为 ViewModelTests.kt 的新类。
5. 添加必要的依赖项
将以下依赖项添加到项目中:
testImplementation 'junit:junit:4.+'
testImplementation 'androidx.arch.core:core-testing:2.1.0'
现在,同步项目。
6. 编写 ViewModel 测试
我们先从一个简单的测试开始:当我们在设备或模拟器上与应用互动时,首先要选择纸杯蛋糕的数量。因此,我们首先要测试 OrderViewModel
中的 setQuantity()
方法,并检查 quantity
LiveData
对象的值。
我们要测试的 quantity
变量是 LiveData
的实例。测试 LiveData
对象需要执行一个额外的步骤,我们添加的依赖项就是要在此处发挥作用。我们使用 LiveData
在值更改后立即更新界面。界面运行在我们所谓的“主线程”上。如果您不熟悉线程和并发也没关系,我们会在其他 Codelab 中对这些内容进行深入介绍。眼下,就 Android 应用而言,您可以将主线程视为界面线程。用于向用户显示界面的代码在此线程上运行。除非另有指定,否则单元测试假定所有代码均在主线程上运行。不过,由于 LiveData
对象无法访问主线程,因此我们必须显式声明 LiveData
对象不应调用主线程。
- 为了指定
LiveData
对象不应调用主线程,我们需要在每次测试LiveData
对象时提供一条特定的测试规则。
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
- 现在,我们可以创建一个名为
quantity_twelve_cupcakes()
的函数。在该方法中,创建OrderViewModel.
的实例。 - 在此测试中,您将检查以确保在调用
setQuantity
时,OrderViewModel
中的quantity
对象会更新。但在调用任何方法或使用OrderViewModel
中的任何数据之前,请务必注意,在测试LiveData
对象的值时,您需要观察各个对象以便发出更改。为此,一个简单的方法就是使用observeForever
方法。对quantity
对象调用observeForever
方法。此方法需要 lambda 表达式,但该表达式可以留空。 - 然后,调用
setQuantity()
方法,并传入12
作为参数。
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
- 我们可以稳稳地推断出,
quantity
对象的值为12
。请注意,LiveData
对象并非值本身。值包含在名为value
的属性中。做出以下断言:
assertEquals(12, viewModel.quantity.value)
您的测试应如下所示:
@Test
fun quantity_twelve_cupcakes() {
val viewModel = OrderViewModel()
viewModel.quantity.observeForever {}
viewModel.setQuantity(12)
assertEquals(12, viewModel.quantity.value)
}
运行测试!恭喜,您刚刚编写了您的首个 LiveData
单元测试,这是 Modern Android Development 中的一项重要技能。此测试无法测试太多业务逻辑,所以,接下来我们要编写一个稍微复杂一些的测试。
OrderViewModel
的一项主要功能是计算订单的价格。当我们选择纸杯蛋糕的数量以及选择取货日期时,都会进行此项计算。价格计算是在一个私有方法中进行的,因此我们的测试无法直接调用该方法。只有 OrderViewModel
中的其他方法可以调用它。这些方法是公共方法,所以我们会调用这些方法来触发价格计算,以便检查价格的值是否符合预期。
最佳做法
选择纸杯蛋糕的数量和选择日期时,价格会更新。虽然这两种情况都应该进行测试,但一般而言,最好只测试一项功能。因此,我们要为每项测试编写单独的方法:一个函数用于测试数量更新时的价格,另一个不同的函数用于测试日期更新时的价格。我们绝不希望因为其他测试失败而导致测试结果失败。
- 创建一个名为
price_twelve_cupcakes()
的方法,并添加注解将其标记为测试。 - 在该方法中,创建一个
OrderViewModel
的实例并调用setQuantity()
方法,传入 12 作为参数。
val viewModel = OrderViewModel()
viewModel.setQuantity(12)
- 查看
OrderViewModel
中的PRICE_PER_CUPCAKE
,可以看到纸杯蛋糕每个 2.00 美元。我们还可以看到,每次初始化ViewModel
时都会调用resetOrder()
,并且在此方法中,默认日期是今天的日期,而PRICE_FOR_SAME_DAY_PICKUP
为 3.00 美元。因此,12 * 2 + 3 = 27。我们的预期是,在选择 12 个纸杯蛋糕后,price
变量的值为 27.00 美元。所以,我们做出断言,预期值 27.00 美元等于price LiveData
对象的值。
assertEquals("$27.00", viewModel.price.value)
现在,运行测试。
应该会失败!
测试结果显示,实际值为 null
。测试对此结果给出了解释。如果查看 OrderViewModel
中的 price
变量,您会看到以下内容:
val price: LiveData<String> = Transformations.map(_price) {
// Format the price into the local currency and return this as LiveData<String>
NumberFormat.getCurrencyInstance().format(it)
}
此示例旨在说明在测试中应观察到 LiveData
的原因。通过使用 Transformation
设置 price
的值。实际上,此代码会将分配给 price
的值转换为货币格式,这样我们就不必手动转换。不过,此代码还有其他作用。转换 LiveData
对象时,除非绝对必要,否则不会调用此代码,这样才能在移动设备上节省资源。只有在我们观察该对象是否发生了更改时,才会调用此代码。当然,这是在应用中完成的,但我们也需要在测试时执行相同的操作。
- 在测试方法中,请在设置数量前添加以下代码行:
viewModel.price.observeForever {}
您的测试应如下所示:
@Test
fun price_twelve_cupcakes() {
val viewModel = OrderViewModel()
viewModel.price.observeForever {}
viewModel.setQuantity(12)
assertEquals("$27.00", viewModel.price.value)
}
现在,如果您运行测试,测试应该会通过。
7. 解决方案代码
8. 恭喜
在此 Codelab 中,我们完成了以下内容:
- 了解如何设置
LiveData
测试。 - 了解如何测试
LiveData
本身。 - 了解如何测试经过转换后的
LiveData
。 - 了解如何在单元测试中观察
LiveData
。