构建本地单元测试

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

}

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("[email protected]"));
  }
}

您应该创建可读的测试,以评估应用程序中的组件是否返回预期结果。我们建议您使用断言库,例如 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. 将 Mockito 库依赖项添加到您的 build.gradle 文件中,如 设置测试环境 中所述。
  2. 在单元测试类定义的开头,添加 @RunWith(MockitoJUnitRunner.class) 注解。此注解告诉 Mockito 测试运行器验证您对框架的使用是否正确,并简化模拟对象的初始化。
  3. 要为 Android 依赖项创建模拟对象,请在字段声明之前添加 @Mock 注解。
  4. 要存根依赖项的行为,可以使用 when()thenReturn() 方法指定满足条件时的条件和返回值。

以下示例显示了如何创建使用 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

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

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

如果抛出的异常对您的测试有影响,您可以更改行为,以便方法改为返回 null 或零,具体取决于返回类型。为此,请在项目的顶层 build.gradle 文件(在 Groovy 中)中添加以下配置

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