翻译官方文档-单元测试

官方文档链接:https://developer.android.google.cn/training/testing/unit-testing/index.html

1.前言


单元测试是应用程序测试策略中的基本测试。通过对代码创建和运行单元测试,可以轻松验证独立逻辑单元是否正确。每次构建后运行单元测试,有助于快速捕获和修复代码改变引入的软件问题。通常以可重复的方式执行尽可能小的代码单元的功能(可能是方法、类或组件)。当需要验证应用程序中指定代码的逻辑,应该构建单元测试。例如,正在单元测试一个类,可能会检查类的状态是否正确。代码单元是独立测试的,只影响和监听指定单元的改变,模拟框架可被用来隔离单元和它的依赖。

注意:单元测试不适合测试复杂的UI交互事件。应该使用UI测试框架,如自动化UI测试中描述的。

为了测试安卓应用程序,通常会创建这些类型的自动化单元测试:

  • 本地测试:只运行于本地机器的单元测试。这些测试编译运行于Java虚拟机(JVM)来减少执行时间,不依赖安卓框架或者可以使用模拟对象替代依赖项。
  • 设备测试:运行在安卓设备或模拟器上的单元测试。这些测试可以访问仪器的信息,例如被测试应用程序的上下文,这些不易被模拟对象替代的安卓依赖项。

下面将介绍如何构建这些类型的自动化单元测试。

2.本地单元测试


如果单元测试没有依赖或仅简单依赖安卓,应该在本地开发机器上运行测试。这种方式有助于避免每次运行测试,都加载目标应用程序和单元测试代码到物理设备或模拟器,大大减少单元测试的执行时间。为配合这种方式,通常使用类似Mockito的模拟框架来满足所有依赖关系。

2.1.设置测试环境

在Android Studio项目中,必须存储本地单元测试的源文件到模块名/src/test/java/目录(创建新项目时已存在)下。还需要使用JUnit 4框架提供的标准APIs,来配置项目的测试依赖。如果测试需要安卓的依赖配合,类似Mockito库可以简化本地单元测试,要了解关于使用模拟对象的更多信息,请参阅模拟Android依赖项

在应用程序顶层build.gradle文件中(即工程目录,若仅哪个模块需要,在该模块下配置),需要指定这些库作为依赖:

dependencies {
    // Required -- JUnit 4 framework
    testCompile 'junit:junit:4.12'
    // Optional -- Mockito framework
    testCompile 'org.mockito:mockito-core:1.10.19'
}
2.2.创建本地单元测试类

本地单元测试类应该写成JUnit 4测试类。JUnit是最流行和广泛使用的Java单元测试框架,它最新的版本相比之前,允许以更简洁和灵活的方式编写测试。不同于基于JUnit 3的Android单元测试的做法,JUnit 4不需要扩展junit.framework.TestCase类,也不需要为测试方法名称加test关键字作为前缀,同时不需要使用junit.frameworkjunit.extensions包中的任何类。

创建基本的JUnit 4测试类(包含一个或多个测试方法的Java类)。每个测试方法以@Test注解开始,且包含用于执行和验证想要测试的组件中单一功能的代码。下面的例子展示如何实现本地单元测试类,测试方法emailValidator_CorrectEmailSimple_ReturnsTrue验证被测试的应用程序中isValidEmail()方法返回结果的正确性。

import org.junit.Test;
import java.util.regex.Pattern;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

public class EmailValidatorTest {

    @Test
    public void emailValidator_CorrectEmailSimple_ReturnsTrue() {
        assertThat(EmailValidator.isValidEmail("name@email.com"), is(true));
    }
    ...
}

为测试应用程序中的组件是否返回期望的结果,使用junit.Assert方法执行验证检查(或断言),来比较被测试组件的状态与一些期望的值。为了让测试更具可读性,可以使用Hamcrest匹配器(例如is()equalTo()方法)来比较返回的结果和预期的结果。

2.3.模拟安卓依赖项

默认情况下,Gradle的安卓插件基于修改过的android.jar库(不包含任何实际代码),执行本地单元测试。在测试方法中调用安卓类时会引发异常,来确保只测试编写的代码,不依赖于Android平台的任何特有行为(当没有显式地模拟时)。

可以使用模拟框架模拟代码中的外部依赖项,从而很容易地按期望的方式测试需要与依赖交互的组件。不仅将单元测试与Android系统的其余部分隔离,同时验证那些依赖项中的正确方法是否被调用。支持Java的Mockito模拟框架(1.9.5版本及以上)提供对安卓单元测试的兼容,可以配置模拟对象被调用时返回一些特定的值。若要使用此框架向本地单元测试添加模拟对象,请遵循以下开发步骤:

  • 按照设置测试环境那节中描述的,在build.gradle文件中添加对Mockito库的依赖。
  • 在单元测试类的定义之前,加上@RunWith(MockitoJUnitRunner.class)注解。这个注解告诉Mockito测试运行器去验证框架的使用是否正确,并且简化模拟对象的初始化。
  • 为安卓依赖项创建模拟对象时,在成员变量声明前添加@Mock注解。
  • 重写依赖项的行为,可以通过使用when()thenReturn()方法,指定一个条件和当条件满足时返回的值。

下面的例子展示如何使用模拟的上下文对象创建单元测试。

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.CoreMatchers.*;
import static org.mockito.Mockito.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import android.content.SharedPreferences;

@RunWith(MockitoJUnitRunner.class)
public class UnitTestSample {

    private static final String FAKE_STRING = "HELLO WORLD";

    @Mock
    Context mMockContext;

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

        // ...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类。

2.4.Error:"Method ... not mocked"

如果运行测试时,调用了安卓SDK中没有模拟的API,将会接收到一个此方法没有被模拟的错误,这是因为运行单元测试使用的android.jar文件不包含任何实际代码(这些APIs仅由设备上安卓系统镜像提供)。通过默认引发异常,来确保只测试编写的代码,不依赖于Android平台的任何特有行为(当没有显式地模拟时,例如使用Mockito)。当不希望测试中抛出异常时,可以通过给项目顶层的build.gradle文件(若仅针对模块,就用模块下的)中添加如下的配置来改变行为,让方法能够返回null或0:

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

注意:设置returnDefaultValues属性为true时,应当谨慎。以null/0作为返回值会在测试中传递,这很难调试,而且可能会导致失败的测试通过,所以把它当作最后的手段。

2.5.运行本地单元测试

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

  • 通过点击工具栏中同步工程按钮,确保项目被Gradle同步。
  • 用下面的方式之一运行测试:
    • 运行单一测试方法,打开Project窗口,然后右击一个测试方法并点击Run选项。
    • 运行类中所有测试方法,右击这个类或测试文件中的方法并点击Run选项。
    • 运行目录下所有测试方法,右击这个目录并点击Run tests选项。

Gradle的安卓插件会编译位于默认目录(src/test/java/)下的本地单元测试代码,构建一个测试应用程序,并且使用默认的测试运行器类来本地执行它,然后Android Studio在Run窗口中展示结果。

3.设备单元测试


设备单元测试是运行在物理设备和模拟器的测试,可以使用安卓框架APIs和支持的APIs,例如安卓测试支持库。当测试需要访问设备信息(例如目标应用程序的上下文)或需要安卓框架组件的真正实现(例如Parcelable或SharedPreferences对象)时,才创建设备单元测试。使用设备单元测试也有助于减少需要编写和维护模拟代码的工作量,同时可以使用模拟框架来模拟任何依赖关系。

3.1.设置测试环境

在Android Studio项目中,必须存储设备测试的源文件到模块名/src/androidTest/java/目录下,此目录创建新项目时已存在并包含设备测试样例。

首先应该下载安卓测试支持库,它提供为应用程序快速构建和运行设备测试代码的APIs,同时包含JUnit 4测试运行器(AndroidJUnitRunner)和UI功能测试(Espresso和UI Automator)所需的APIs。接着,需要配置工程的安卓测试依赖项,来使用测试支持库提供的测试运行器和规定的APIs。为了简化测试开发,也应该包含Hamcrest库,从而使用它的匹配APIs来创建更灵活的断言。

在应用程序顶层build.gradle文件中(即工程目录,若仅哪个模块需要,在该模块下配置),需要指定这些库作为依赖:

dependencies {
    androidTestCompile 'com.android.support:support-annotations:24.0.0'
    androidTestCompile 'com.android.support.test:runner:0.5'
    androidTestCompile 'com.android.support.test:rules:0.5'
    // Optional -- Hamcrest library
    androidTestCompile 'org.hamcrest:hamcrest-library:1.3'
    // Optional -- UI testing with Espresso
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
    // Optional -- UI testing with UI Automator
    androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
}

注意:如果构建配置的依赖包括compile注解支持库和androidTestCompileEspresso核心库,那么依赖冲突可能会导致构建失败。要解决此问题,按下面的方式更新对Espresso核心库的依赖:

androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
})

为了使用JUnit 4测试类,通过在app模块下的build.gradle文件中添加以下设置,确保指定AndroidJUnitRunner作为工程的默认设备测试运行器:

android {
    defaultConfig {
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
}
3.2.创建设备单元测试类

设备单元测试类应该写成JUnit 4测试类。要了解更多关于创建JUnit 4测试类和使用JUnit 4断言及注解,请参阅创建本地单元测试类。为创建设备的JUnit 4测试类,在定义此类之前添加@RunWith(AndroidJUnit4.class)注解,也需要指定安卓测试支持库提供的AndroidJUnitRunner类为默认测试运行器。下面例子展示如何编写设备单元测试,来验证LogHistory类是否正确实现了Parcelable接口:

import android.os.Parcel;
import android.support.test.runner.AndroidJUnit4;
import android.util.Pair;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.List;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertThat;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class LogHistoryAndroidUnitTest {

    public static final String TEST_STRING = "This is a string";
    public static final long TEST_LONG = 12345678L;
    private LogHistory mLogHistory;

    @Before
    public void createLogHistory() {
        mLogHistory = new LogHistory();
    }

    @Test
    public void logHistory_ParcelableWriteRead() {
        // Set up the Parcelable object to send and receive.
        mLogHistory.addEntry(TEST_STRING, TEST_LONG);

        // Write the data.
        Parcel parcel = Parcel.obtain();
        mLogHistory.writeToParcel(parcel, mLogHistory.describeContents());

        // After you're done with writing, you need to reset the parcel for reading.
        parcel.setDataPosition(0);

        // Read the data.
        LogHistory createdFromParcel = LogHistory.CREATOR.createFromParcel(parcel);
        List<Pair<String, Long>> createdFromParcelData = createdFromParcel.getData();

        // Verify that the received data is correct.
        assertThat(createdFromParcelData.size(), is(1));
        assertThat(createdFromParcelData.get(0).first, is(TEST_STRING));
        assertThat(createdFromParcelData.get(0).second, is(TEST_LONG));
    }
}
3.3.创建测试套件

为组织设备单元测试的执行,可以收集一系列测试类到一个测试套件类中,然后一起运行这些测试。测试套件可以被嵌套,即收集其它测试套件到自己测试套件中,然后一起运行所有的测试类。测试套件包含在测试包中,类似于主应用程序包,命名通常以.suite结尾作为后缀(例如,com.example.android.testing.mysample.suite)。

为单元测试创建测试套件,需导入JUnit中RunWith和Suite类。在套件中添加@RunWith(Suite.class)@Suite.SuitClasses()注解,并在@Suite.SuitClasses()注解中分别列出测试类或测试套件作为参数。下面的例子展示,如何实现名为UnitTestSuite的测试套件,它收集并一起运行 CalculatorInstrumentationTest和CalculatorAddParameterizedTest测试类。

import com.example.android.testing.mysample.CalculatorAddParameterizedTest;
import com.example.android.testing.mysample.CalculatorInstrumentationTest;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;

// Runs all unit tests.
@RunWith(Suite.class)
@Suite.SuiteClasses({CalculatorInstrumentationTest.class,
        CalculatorAddParameterizedTest.class})
public class UnitTestSuite {}
3.4.运行设备单元测试

按照下面这些步骤运行设备测试:

  • 通过点击工具栏中同步工程按钮,确保项目被Gradle同步。
  • 用下面的方式之一运行测试:
    • 运行单一测试方法,打开Project窗口,然后右击一个测试方法并点击Run选项。
    • 运行类中所有测试方法,右击这个类或测试文件中的方法并点击Run选项。
    • 运行目录下所有测试方法,右击这个目录并点击Run tests选项。

Gradle的安卓插件会编译位于默认目录(src/androidTest/java/)下的设备测试代码,构建一个测试应用包和产品应用包,安装到连接的设备或模拟器上,并且运行测试,随后Android Studio在Run窗口中展示结果。

注意:当运行或调试设备测试时,Android Studio不注入Instant Run所需的额外方法,并将功能关闭。

3.5.在Firebase上运行测试

使用Firebase测试实验室,可以在谷歌数据中心的物理和虚拟设备中,选择多款流行安卓设备和不同配置(地区、横竖屏、屏幕尺寸和平台版本),同时测试应用程序。可以从Android Studio或命令行,直接部署应用程序到测试实验室。测试结果提供日志,并包括应用程序失败的所有详细信息。

在开始使用Firebase测试实验室之前,需要做到以下几点,除非已经拥有谷歌账号和Firebase工程:

  • 如果还没有,创建谷歌账号。
  • 在Firebase控制台中,点击Create New Project选项。

在Spark计划的每日免费额度内,使用测试实验室测试应用程序不收取费用。Android Studio提供集成工具,用来配置希望如何部署测试到Firebase测试实验室。当按照规定的步骤创建完Firebase工程,就可以创建测试配置和运行测试:

  • 在主菜单点击Run > Edit Configurations选项。
  • 点击Add New Configuration选项并选择Android Tests
  • 在安卓测试配置对话框内:
    • 输入或选择测试的详细信息,例如测试名称、模块类型、测试类型和测试类。
    • Deployment Target Options功能区的Target下拉菜单中,选择Firebase Test Lab Device Matrix选项。
    • 如果还没有登录,点击Connect to Google Cloud Platform,并允许Android Studio访问自己的账户。
    • 接着是Cloud Project,点击Settings按钮并从列表中选择自己的Firebase工程。
  • 创建和配置测试矩阵:
    • 接着是Matrix Configuration下拉列表,点击Open Dialog按钮。
    • 点击Add New Configuration (+)
    • Name字段处,输入新配置的名字。
    • 选择想要测试应用程序的设备、安卓版本、区域和横竖屏。Firebase测试实验室将在选择的每种组合下测试应用程序,并生成测试结果。
    • 点击OK保存配置。
  • 点击Run/Debug Configurations对话框中的OK按钮退出。
  • 通过点击Run按钮运行测试。
Config.png

当Firebase测试实验室完整运行了测试,Run窗口将打开并显示结果,如下图所示。可能需要点击Show Passed按钮来查看所有执行过的测试。

Results.png

也可以通过点击Run窗口中,显示在测试执行日志开头的链接,到网页上分析测试。要了解更多关于网页展示结果的分析,请参阅分析Firebase安卓测试实验室的结果

3.6.附加示例代码

要下载关于设备单元测试的示例应用程序,请参阅Android ActivityInstrumentation Sample

4.总结


单元测试可以说是程序员在开发时,最常用的自检技术。通过它确定业务逻辑当中,输入和输出的对应关系(与函数式编程观点类似,有兴趣可以研究),方便开发人员确定自己编码的正确性,减少安装到设备上调试的频率,大大提高了工作效率,希望大家可以重视。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 官方文档链接:https://developer.android.google.cn/training/testi...
    lanceJin阅读 788评论 0 4
  • 仪器单元测试是在物理设备和模拟器上运行的测试,它们可以利用Android framework APIs和suppo...
    耳东CY阅读 484评论 0 0
  • 5元能做什么呢? 路边摊上买一碗凉面(店铺里面卖8元)。 10元能做什么呢? 网上买一本儿童绘本(忽略精装版)。 ...
    花与熙阅读 242评论 0 1
  • --01-- 世界上从来不缺少急忙赶路的人,也从来不缺少路过你身边时带过一阵风的人,只是少了些驻足沉吟的人。 人们...
    谙忞阅读 646评论 0 1