构建本地单元测试

本地测试直接在您自己的工作站上运行,而不是在 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() 方法时始终返回相同的值。

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

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

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

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

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

错误:“方法...未被模拟”

如果您尝试访问可模拟 Android 库的任何方法,并且其消息为 Error: "Method ... not mocked,则该库会抛出异常。

如果抛出的异常对您的测试造成问题,您可以更改行为,使方法根据返回类型返回 null 或零。为此,请在您项目的顶级 build.gradle 文件(采用 Groovy 语言)中添加以下配置:

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