构建本地单元测试

当您需要更快地运行测试而不需要与在真实设备上运行测试关联的保真度和置信度时,可以使用本地单元测试来评估应用的逻辑。对于这种方法,您通常使用 Robolectric 或模拟框架(如 Mockito)来实现依赖项。通常,与测试关联的依赖项类型决定了您使用的工具:

  • 如果您的测试对 Android 框架有依赖性(特别是与框架建立复杂互动的测试),最好使用 Robolectric 添加框架依赖项
  • 如果您的测试对 Android 框架的依赖性极小,或者如果测试仅取决于您自己的对象,可以使用诸如 Mockito 之类的模拟框架添加模拟依赖项

设置测试环境

在 Android Studio 项目中,您必须将本地单元测试的源文件存储在 module-name/src/test/java/ 中。当您创建新项目时,此目录已存在。

您还需要为项目配置测试依赖项,以使用 JUnit 4 框架提供的标准 API。如果您的测试需要与 Android 依赖项互动,请添加 Robolectric 或 Mockito 库以简化您的本地单元测试。

在应用的顶级 build.gradle 文件中,请将以下库指定为依赖项:

    dependencies {
        // Required -- JUnit 4 framework
        testImplementation 'junit:junit:4.12'
        // Optional -- Robolectric environment
        testImplementation 'androidx.test:core:1.0.0'
        // Optional -- Mockito framework
        testImplementation 'org.mockito:mockito-core:1.10.19'
    }
    

创建本地单元测试类

您的本地单元测试类应编写为 JUnit 4 测试类。JUnit 是最受欢迎且应用最广泛的 Java 单元测试框架。与原先的版本相比,JUnit 4 可让您以更简洁且更灵活的方式编写测试,因为 JUnit 4 不要求您执行以下操作:

  • 扩展 junit.framework.TestCase 类。
  • 在测试方法名称前面加上 'test' 关键字作为前缀。
  • 使用 junit.frameworkjunit.extensions 软件包中的类。

如需创建基本的 JUnit 4 测试类,请创建包含一个或多个测试方法的类。测试方法以 @Test 注释开头,并且包含用于运用和验证要测试的组件中的单项功能的代码。

以下示例展示了如何实现本地单元测试类。测试方法 emailValidator_CorrectEmailSimple_ReturnsTrue 验证被测应用中的 isValidEmail() 方法是否返回正确的结果。

Kotlin

    import com.google.common.truth.Truth.assertThat
    import org.junit.Test

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

Java

    import com.google.common.truth.Truth.assertThat;
    import org.junit.Test;

    public class EmailValidatorTest {
        @Test
        public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
            assertThat(EmailValidator.isValidEmail("name@email.com")).isTrue();
        }
    }
    

如需创建容易读懂的测试来评估应用中的组件是否返回预期的结果,我们建议使用 Truth 库和 Android Assertions 中的类,如前面的示例所示。如需详细了解 Truth 和 Android Assertions 支持哪些类型的逻辑验证,请参阅介绍如何创建更容易读懂的断言的部分。

不过,如果您更愿意使用 junit.Assert 方法或 Hamcrest 匹配器(如 is()equalTo() 方法)来比较预期结果与实际结果,也可以改用这些库。

注意:Hamcrest 仍然是构建匹配器(例如,为 Espresso 的 ViewMatcher 类构建匹配器)时使用的首选库。

添加框架依赖项

如果您的测试与多个 Android 框架依赖项互动,或以复杂的方式与这些依赖项互动,请使用 AndroidX Test 提供的 Robolectric 工件。Robolectric 在本地 JVM 或真实设备上执行真实的 Android 框架代码和原生框架代码的虚假对象。

以下示例展示了如何创建使用 Robolectric 的单元测试:

app/build.gradle

    android {
        // ...
        testOptions {
            unitTests.includeAndroidResources = true
        }
    }
    

MyLocalUnitTestClass

Kotlin

    import android.content.Context
    import androidx.test.core.app.ApplicationProvider
    import com.google.common.truth.Truth.assertThat
    import org.junit.Test

    private const val FAKE_STRING = "HELLO_WORLD"

    class UnitTestSample {
        val context = ApplicationProvider.getApplicationContext<Context>()

        @Test fun readStringFromContext_LocalizedString() {
            // Given a Context object retrieved from Robolectric...
            val myObjectUnderTest = ClassUnderTest(context)

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

            // ...then the result should be the expected one.
            assertThat(result).isEqualTo(FAKE_STRING)
        }
    }
    

Java

    import android.content.Context;
    import androidx.test.core.app.ApplicationProvider;
    import org.junit.Test;

    import static com.google.common.truth.Truth.assertThat;

    public class UnitTestSampleJava {
        private static final String FAKE_STRING = "HELLO_WORLD";
        private Context context = ApplicationProvider.getApplicationContext();

        @Test
        public void readStringFromContext_LocalizedString() {
            // Given a Context object retrieved from Robolectric...
            ClassUnderTest myObjectUnderTest = new ClassUnderTest(context);

            // ...when the string is returned from the object under test...
            String result = myObjectUnderTest.getHelloWorldString();

            // ...then the result should be the expected one.
            assertThat(result).isEqualTo(FAKE_STRING);
        }
    }
    

添加 Android Builder 类

如果您要创建在 Robolectric 环境中或真实设备上运行的本地单元测试,则可以使用 AndroidX Test 为几个常见框架类提供的构建程序。这些构建程序可让您创建以下类的实例,而无需使用模拟或反射:

使用 Parcelable 实用程序类

此外,该库还为 Parcelable 对象提供了一个实用程序类。通过提供 Creator 对象,此类将解组给定的 Parcelable 对象,然后编组重复的 Parcelable 对象。

注意:由调用 Parcelables.forceParcel() 的方法来验证解组/重新编组操作是否成功。

添加模拟依赖项

默认情况下,Android Plug-in for Gradle 针对一个修改版 android.jar 库(不包含任何实际代码)执行本地单元测试。从单元测试对 Android 类的方法调用会抛出异常。这是为了确保您仅测试代码,而不依赖于 Android 平台的任何特定行为,即您未明确构建或模拟的行为。

模拟 Android 依赖项

如果您的测试对 Android 的依赖性极小,并且您需要在应用中测试组件与其依赖项之间的特定互动,请使用模拟框架对代码中的外部依赖项打桩。这样,您就可以轻松地测试组件是否按预期方式与依赖项互动。通过用模拟对象代替 Android 依赖项,您可以将单元测试与 Android 系统的其余部分隔离,同时验证是否调用了这些依赖项中的正确方法。适用于 Java(版本 1.9.5 及更高版本)的 Mockito 模拟框架提供了与 Android 单元测试的兼容性。通过 Mockito,您可以将模拟对象配置为在被调用时返回某个特定值。

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

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

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

Kotlin

    import android.content.Context
    import com.google.common.truth.Truth.assertThat
    import org.junit.Test
    import org.junit.runner.RunWith
    import org.mockito.Mock
    import org.mockito.Mockito.`when`
    import org.mockito.junit.MockitoJUnitRunner

    private const val FAKE_STRING = "HELLO WORLD"

    @RunWith(MockitoJUnitRunner::class)
    class UnitTestSample {

        @Mock
        private lateinit var mockContext: Context

        @Test
        fun readStringFromContext_LocalizedString() {
            // Given a mocked Context injected into the object under test...
            `when`(mockContext.getString(R.string.hello_word))
                    .thenReturn(FAKE_STRING)
            val myObjectUnderTest = ClassUnderTest(mockContext)

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

            // ...then the result should be the expected one.
            assertThat(result, `is`(FAKE_STRING))
        }
    }
    

Java

    import android.content.Context;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.mockito.Mock;
    import org.mockito.junit.MockitoJUnitRunner;

    import static com.google.common.truth.Truth.assertThat;
    import static org.mockito.Mockito.when;

    @RunWith(MockitoJUnitRunner.class)
    public class UnitTestSample {

        private static final String FAKE_STRING = "HELLO WORLD";

        @Mock
        Context mockContext;

        @Test
        public void readStringFromContext_LocalizedString() {
            // Given a mocked Context injected into the object under test...
            when(mockContext.getString(R.string.hello_world))
                    .thenReturn(FAKE_STRING);
            ClassUnderTest myObjectUnderTest = new ClassUnderTest(mockContext);

            // ...when the string is returned from the object under test...
            String result = myObjectUnderTest.getHelloWorldString();

            // ...then the result should be the expected one.
            assertThat(result, is(FAKE_STRING));
        }
    }
    

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

错误:“Method ... not mocked”

如果您运行的测试从并未模拟的 Android SDK 调用 API,您会收到一条错误,指出未模拟此方法。这是因为,用于运行单元测试的 android.jar 文件不包含任何实际代码(这些 API 仅由设备上的 Android 系统映像提供)。

默认情况下,所有方法都会抛出异常。这是为了确保单元测试仅测试代码,而不依赖于 Android 平台的任何特定行为,即您未明确模拟(如使用 Mockito 模拟)的行为。

如果抛出的异常会给测试带来问题,您可以通过在项目的顶级 build.gradle 文件中添加以下配置来更改行为,以使方法返回 null 或 0:

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

注意:将 returnDefaultValues 属性设为 true 时应格外小心。null/0 返回值会在测试中引入回归,这很难调试,并且可能会允许失败的测试通过。只有在万不得已时,才能使用此配置。

运行本地单元测试

要运行本地单元测试,请按以下步骤操作:

  1. 点击工具栏中的 Sync Project 图标 ,确保您的项目与 Gradle 同步。
  2. 通过以下方式之一来运行测试:
    • 如需运行单个测试,请打开 Project 窗口,右键点击一个测试,然后点击 Run 图标
    • 如需测试一个类中的所有方法,请右键点击测试文件中的一个类或方法,然后点击 Run 图标
    • 如需运行一个目录中的所有测试,请右键点击该目录,然后选择 Run tests 图标

Android Plugin for Gradle 会编译位于默认目录 (src/test/java/) 中的本地单元测试代码,构建一个测试应用,并使用默认测试运行程序类在本地执行该测试应用。Android Studio 随后会在 Run 窗口中显示结果。