构建本地单元测试

2024-06-12 10:10
96
0

本地测试直接在您自己的工作站上运行,而不是在 Android 设备或模拟器上运行。因此,它使用本地 Java 虚拟机 (JVM) 而不是 Android 设备来运行测试。本地测试可让您更快地评估应用的逻辑。不过,无法与 Android 框架进行交互会限制您可以运行的测试类型。

单元测试可验证一小段代码(受测单元)的行为。为此,它会执行该代码并检查结果。

单元测试通常很简单,但如果受测单元的设计没有考虑到可测试性,则单元测试的设置可能会出现问题:

  • 您要验证的代码必须可通过测试访问。例如,您无法直接测试私有方法。而是使用该类的公共 API 对其进行测试。
  • 为了在隔离模式下运行单元测试,必须将被测单元测试的依赖项替换为您可以控制的组件,例如虚构组件或其他测试替身。如果您的代码依赖于 Android 框架,此问题尤其明显。

如需了解 Android 中的常见单元测试策略,请参阅测试内容

本地测试位置

默认情况下,本地单元测试的源文件位于 module-name/src/test/ 中。当您使用 Android Studio 创建新项目时,此目录已存在。

添加测试依赖项

您还需要为项目配置测试依赖项,以使用 JUnit 测试框架提供的标准 API。

为此,请打开应用的模块的 build.gradle 文件,并将以下库指定为依赖项。使用 testImplementation 函数来指明它们适用于本地测试源代码集,而非应用:

dependencies {
 
// Required -- JUnit 4 framework
  testImplementation
"junit:junit:$jUnitVersion"
 
// Optional -- Robolectric environment
  testImplementation
"androidx.test:core:$androidXTestVersion"
 
// Optional -- Mockito framework
  testImplementation
"org.mockito:mockito-core:$mockitoVersion"
 
// Optional -- mockito-kotlin
  testImplementation
"org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
 
// Optional -- Mockk framework
  testImplementation
"io.mockk:mockk:$mockkVersion"
}

注意 testImplementation 会添加本地测试的依赖项,而 androidTestImplementation 则会添加插桩测试的依赖项。

创建本地单元测试类

您可以将本地单元测试类编写为 JUnit 4 测试类。

为此,请创建一个包含一个或多个测试方法的类(通常位于 module-name/src/test/ 中)。测试方法以 @Test 注解开头,包含用于练习和验证要测试的组件的一个方面的代码。

以下示例演示了如何实现本地单元测试类。测试方法 emailValidator_correctEmailSimple_returnsTrue() 会尝试验证 isValidEmail(),这是应用中的方法。如果 isValidEmail() 也返回 true,测试函数也将返回 true。


import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class EmailValidatorTest {
 
@Test fun emailValidator_CorrectEmailSimple_ReturnsTrue() {
    assertTrue
(EmailValidator.isValidEmail("name@email.com"))
 
}

}

您应创建可读性测试,以评估应用中的组件是否返回预期结果。我们建议您使用断言库,例如 junit.AssertHamcrestTruth。上面的代码段示例展示了如何使用 junit.Assert

可模拟的 Android 库

当您执行本地单元测试时,Android Gradle 插件会包含一个库,其中包含 Android 框架的所有 API,并且正确的版本与您项目中使用的版本一致。该库包含这些 API 的所有公共方法和类,但方法中的代码已被移除。如果访问了其中任何方法,测试会抛出异常。

这样,就可以在引用 Android 框架中的类(例如 Context)时构建本地测试。更重要的是,它允许您将模拟框架与 Android 类一起使用。

注意 :模拟是一种测试替身,它对互动有预期,可以定义其行为。请参阅测试替身

模拟 Android 依赖项

一个典型的问题是发现某个类使用的是字符串资源。您可以通过调用 Context 类中的 getString() 方法来获取字符串资源。不过,本地测试无法使用 Context 或其任何方法,因为它们属于 Android 框架。理想情况下,对 getString() 的调用会从类中移出,但并不总是切实可行。解决方案是创建一个 Context 的模拟或桩,使其在调用其 getString() 方法时始终返回相同的值。

借助 Mockable Android 库和 MockitoMockK 等模拟框架,您可以对单元测试中的 Android 类模拟的行为编程。

如需使用 Mockito 将模拟对象添加到本地单元测试,请遵循以下编程模型:

  1. 将 Mockito 库依赖项添加到 build.gradle 文件中,如设置测试环境中所述。
  2. 在单元测试类定义的开头,添加 @RunWith(MockitoJUnitRunner.class) 注解。此注解会告知 Mockito 测试运行程序验证您对框架的使用是否正确,并简化模拟对象的初始化。
  3. 如需为 Android 依赖项创建模拟对象,请在字段声明前面添加 @Mock 注解。
  4. 如需对依赖项的行为进行存根,您可以使用 when()thenReturn() 方法指定条件以及满足该条件时的返回值。

以下示例展示了如何创建使用 Kotlin 中通过 Mockito-Kotlin 创建的模拟 Context 对象的单元测试。

import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock

private const val FAKE_STRING = "HELLO WORLD"

@RunWith(MockitoJUnitRunner::class)
class MockedContextTest {

 
@Mock
 
private lateinit var mockContext: Context

 
@Test
 
fun readStringFromContext_LocalizedString() {
   
// Given a mocked Context injected into the object under test...
   
val mockContext = mock<Context> {
        on
{ getString(R.string.name_label) } doReturn FAKE_STRING
   
}

   
val myObjectUnderTest = ClassUnderTest(mockContext)

   
// ...when the string is returned from the object under test...
   
val result: String = myObjectUnderTest.getName()

   
// ...then the result should be the expected one.
    assertEquals
(result, FAKE_STRING)
 
}
}

如需详细了解如何使用 Mockito 框架,请参阅 Mockito API 参考文档示例代码中的 SharedPreferencesHelperTest 类。此外,您也可以试用 Android 测试 Codelab

注意 :应避免使用复杂的模拟。您可以改为使用不同类型的测试替身,例如虚假对象或 Robolectric 影子(如果它们是 Android 类)。此外,还应考虑在插桩测试中使用依赖项的实际实现。

错误:“Method ... not mocked”

如果您尝试通过 Error: "Method ... not mocked 消息访问 Mockable Android 库的任何方法,它会抛出异常。

如果抛出的异常给测试带来了问题,您可以更改行为,使方法返回 null 或 0,具体取决于返回值类型。为此,请在 Groovy 中向项目的顶级 build.gradle 文件中添加以下配置:

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

注意returnDefaultValues 属性设置为 true 时应格外小心。null/零返回值可能会导致测试回归,这难以调试,并且可能会导致失败的测试通过。只有在万不得已时,才应采取此措施。

全部评论