构建本地单元测试

本地测试直接在您自己的工作站上运行,而不是在 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"
}

创建本地单元测试类

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

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

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

Kotlin


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

}

Java


import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

class EmailValidatorTest {
  @Test
  public void 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

错误:“Method ... not mocked”

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

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

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