Gradle for Android-运行测试

为了确保任意app或library的质量,自动化测试是很重要的。长期以来,Android开发工具都不支持自动化测试,但是最近,Google投入巨大精力使得开发者更易启动测试。一些老旧的框架已经被更新,而且一些新的框架也已被添加,确保我们彻底地测试app和library。我们不仅可从AS中运行它们,也可以使用gradle,直接从命令行接口中运行。

本章节中,我们将会探索不同的测试Android app和library的方式。我们也会了解gradle是如何帮助自动化测试的。

本章中,我们会涉及以下主题

  • 单元测试
  • 功能测试
  • 测试覆盖

单元测试

在项目中有写的非常好的单元测试不仅仅可以保障质量,也使得检测新的代码是否影响到其他功能更加容易。AS和Gradle Android插件已经支持了单元测试,但是使用之前需要配置一些东西。

JUnit

JUnit是一个已经存在超过10年非常流行的单元测试library。它使得书写测试更加容易而且确保它们都更易被阅读。但须记住这些特殊的单元测试仅适用于业务逻辑而非与Android SDK关联的的代码。

在为程序写JUnit测试之前,需要创建一个用于测试的目录。按照惯例,这个目录被称为test而且应该和主目录位于同一目录层次。目录结构如下所观:

app
└─── src
        ├─── main
        │      ├─── java
        │      │      └─── com.example.app
        │      └───res
        └─── test
                └─── java
                       └─── com.example.app

然后可以在src/test/java/com.example.app下创建测试类。

为了利用JUnit的最新特性,使用JUnit版本4。可通过在test的build中添加一个依赖保证这点:

dependencies {
    testCompile 'junit:junit:4.12'
}

注意,此处我们使用testCompile代替compile。使用该配置确保该依赖仅当我们运行测试时才会构建,而非打包分发app时。使用testCompile添加的依赖将从不会被assemble tasks泰诺健到发布版本的APK中。

如果在build type或product flavors中有特殊的条件,可能会在特定的build中单独添加一个测试所用的依赖。例如,如果仅想在付费flavor中添加JUnit测试,可做如下所观:

dependencies {
    testPaidCompile 'junit:junit:4.12'
}

万事俱备,是时候开始写一些测试了。这是一个简单的关于一个类测试一个添加两个数字的方法的例子:

import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class LogicTest {
    @Test
    public void addingNegativeNumberShouldSubtract() {
        Logic logic = new Logic();
        assertEquals("6 + -2 must be 4", 4, logic.add(6, -2));
        assertEquals("2 + -5 must be -3", -3, logic.add(2, -5));
    }
}

为了使用gradle运行所有测试,仅执行gradlew测试即可。如果想在一个确定的build变体中运行测试,简单地添加变体名称即可。如果想在debug变体中运行测试,执行gradlewtestDebug。如果测试失败,gradle会在命令行接口打印错误信息。如果所有测试顺利运行,gradle会显示常规的BUILD SUCCESSFUL信息。

一个单独的失败测试会导致test task失败,整个进程都会立刻中止。这意味着万一出现了失败,不是所有的task都会被执行。如果想要确保对所有的build变体,整个测试套件被执行,使用continue标签:

$ gradlew test --continue

也可以通过在相应目录存储测试类专门针对某一确定的build类型写测试。例如,如果想在app的付费版本中测试特定的行为,把类放到src/testPaid/java/com.example.app.

如果不想运行整个的测试套件,而是针对某个特殊的类的测试,可使用test标签,如下所观:

$ gradlew testDebug --tests="*.LogicTest"

执行test task不仅会运行所有的测试,而且也会创建一个test上报。可在app/build/reports/tests/debug/index.xml中发现。该上报使得如果出现了失败,发现问题更容易,而且在自动执行测试情况下格外有用。gradle将会为你运行测试的每一个build变体创建一个上报。

如果所有测试都运行成功,单元测试报告如下:

这里写图片描述

也可以在AS中运行测试,这么做的时候,会在IDE中获得及时反馈,而且可以点击失败的测试导向到相应的代码。如果测试全部通过,Run工具窗口将会如下:

这里写图片描述

如果想运行包含了Android特定类或资源引用的部分代码,常规的单元测试还不够完善。你可能已经尝试并遭遇到java.lang.RuntimeException: Stub!错误。为了修复这个问题,需要实现你自己的Android SDK中的每一个方法,或使用一个模拟框架。幸运的是,存在几个library已经照顾到Android SDK。最流行的就是Robolectric,它提供一个容易的方式测试Android功能,而不需要设备或模拟器。

Robolectric

使用Robolectric,可以书写使用了Android SDK或资源的测试,当还在Java虚拟机内部运行测试的时候。这意味着你不需要一个运行的设备或模拟器利用在测试中的Android资源。因此使得测试app或library中的UI组件的行为更快。

为了开始Robolectric,需要添加几个测试依赖。除了Robolectric自身之外,还需要添加JUnit。如果你想利用支持包,也需要Robolectric影子类使用它:

apply plugin: 'org.robolectric'
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    testCompile 'junit:junit:4.12'
    testCompile'org.robolectric:robolectric:3.0'
    testCompile'org.robolectric:shadows-support:3.0'
}

Robolectric测试类应在src/test/java/com.example.app目录下创建,就像普通的单元测试一样。差异在于你现在可以书写调用了Android类和资源的测试。例如,这个测试验证了一个确定的TextView的文本变化当点击一个特地的Button之后。

@RunWith(RobolectricTestRunner.class)
@Config(manifest = "app/src/main/AndroidManifest.xml", sdk = 18)
public class MainActivityTest {
    @Test
    public void clickingButtonShouldChangeText() {
        AppCompatActivity activity = Robolectric.buildActivity
    (MainActivity.class).create().get();
        Button button = (Button)
        activity.findViewById(R.id.button);
        TextView textView = (TextView)
        activity.findViewById(R.id.label);
        button.performClick();
        assertThat(textView.getText().toString(), equalTo
(activity.getString(R.string.hello_robolectric)));
}
}

Robolectric有一些已知的问题在Android Lollipop和兼容包。如果运行出错提示缺失与兼容包相关的资源,可以修复它。你需要添加添加一个文件到一个称之为project.properties的模块中,并添加如下代码:

android.library.reference.1=../../build/intermediates/
exploded-aar/com.android.support/appcompat-v7/22.2.0
android.library.reference.2=../../build/intermediates/
exploded-aar/com.android.support/support-v4/22.2.0

可以帮助Robolectric发现兼容包资源

功能测试

功能测试是用来测试app的几个组件是否按照预期一起工作。例如,可以创建一个功能测试确保点击一个按钮打开一个新的Activity。对于Android来说有好几个功能测试框架,但是最容易开始功能测试的方式就是使用Espresso框架。

Espresso

Google创建Espresso使得开发者书写功能测试更容易。这个library通过Android支持仓库被提供,所以你可以使用SDK Manager安装它。

为了在设备上运行测试,需要定义一个测试runner。通过测试支持库,Google提供了AndroidJUnitRunner测试runner,它将帮助你在Android设备上运行JUnit测试类。测试runner将会加载app APK和测试APK到设备上,运行所有的测试,然后构建测试结果报告。

加入你已经下载了测试支持库,这就是你应该如果建立测试runner:

defaultConfig {
    testInstrumentationRunner
"android.support.test.runner.AndroidJUnitRunner"
}

你也需要建立几个新的依赖在使用Espresso之前:

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:22.2.0'
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile 'com.android.support.test:rules:0.3'
    androidTestCompile
'com.android.support.test.espresso:espresso-core:2.2'
    androidTestCompile
'com.android.support.test.espresso:espresso-contrib:2.2'
}

需要引用测试支持库和espresso-core启动Espresso。最后的依赖espresso-contrib是一个具有增补Espresso特性的库,但不是core库的一部分。

注意这些依赖使用androidTestCompile配置,代替我们之前使用的testCompile配置。这在单元测试和功能测试之间做了区分。

此时如果你要尝试运行测试build,可能会报错:

Error: duplicate files during packaging of APK app-androidTest.apk
    Path in archive: LICENSE.txt
    Origin 1: ...\hamcrest-library-1.1.jar
    Origin 2: ...\junit-dep-4.10.jar

错误自身非常具有描述性。gradle因为一个重复的文件无法完成构建。幸运的是,仅是一个许可描述,所以我们可以从build中把它删除。错误自身也包含了如何去做的信息:

You can ignore those files in your build.gradle:
android {
packagingOptions {
    exclude 'LICENSE.txt'
    }
}

一旦build文件建立,可以开始添加测试。功能测试较之常规单元测试位于一个不同的目录下。就像依赖配置一样,需要使用androidTest代替仅使用test,所以对于功能测试来说正确的目录是src/androidTest/java/com.example.app。这是一个检查在MainActivity中的TextView的文本是否正确的测试类的的例子:

@RunWith(AndroidJUnit4.class)
@SmallTest
public class TestingEspressoMainActivityTest {
    @Rule
    public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule<>(MainActivity.class);
    @Test
    public void testHelloWorldIsShown() {
        onView(withText("Hello world!")).check(matches(isDisplayed()));
    }
}

在能使用Espresso测试之前,需要确保你有个设备或模拟器。如果忘记连接设备,尝试执行测试task将会抛出异常:

Execution failed for task ':app:connectedAndroidTest'.
>com.android.builder.testing.api.DeviceException:
java.lang.RuntimeException: No connected devices!

一旦已经连接上一个设备或启动了一个模拟器,可以使用gradlewconnectedCheck运行Espresso测试。这个task将会执行connectedAndroidTest在所有已连接的设备上的debug build上运行所有的测试,执行createDebugCoverageReport创建一个测试报告。

你会在app目录下的build/outputs/reports/androidTests/connected目录下发现生成的报告。打开index.xml查看报告,如下所观:

这里写图片描述

功能测试报告展示了测试运行的设备和Android版本。可以同时在多个设备上运行这些测试,所以设备信息使得发现设备或版本特定的bug更容易。

如果想在AS内部获得测试反馈,建立一个run/debug配置直接从IDE运行测试。一个run/debug配置代表了一系列的run/debug安装属性。AS工具栏有个配置选择器,你可以在那里选择你想使用的run/debug配置。

这里写图片描述

为了建立一个新的配置,通过点击Edit Configurations打开配置编辑器…然后创建一个新的Android测试配置。选择模块和指定instrumentation runner为AndroidJUnitRunner,如下截图所示:

这里写图片描述

一旦保存该配置,可以在配置选择器中选择,并点击Run按钮运行所有测试。

从AS中运行Espresso测试有个警告:测试报告没有生成。原因是AS执行connectedAndroidTest task代替connectedCheck。而connectedCheck就是生成测试报告的task。

测试范围

一旦开始为你的Android项目写测试,了解测试覆盖率是很好的。有很多Java的测试覆盖率工具,但Jacoco是最流行的一个。它也是默认添加的,这使得它很容易开始。

Jacoco

使得覆盖率上报时很容易的事。只需要在当前测试的build type中设置testCoverageEnabled = true。使得debug build type测试覆盖率如下:

buildTypes {
    debug {
        testCoverageEnabled = true
    }
}

当使能测试覆盖时,执行gradlew connectedCheck时会创建覆盖率报告。创建报告自身的是createDebugCoverageReport。虽然没有记录,而且当你运行gradlew tasks也不会出现在task列表中,但可能直接运行。然而,因为createCoverateReport依赖connectedCheck,不能分开执行它们。依赖于connectedCheck也意味着你需要一个已连接的设备或模拟器生成测试覆盖率报告。

task执行之后,可在app/build/outputs/reports/coverage/debug/index.xml目录下发现测试报告。每个build变体都有自己的report目录,因为每个变体都可以有不同的测试。测试覆盖率报告如下所观:

这里写图片描述

报告在类层次展示了覆盖率的一个漂亮概览,而且可以点击获取更多信息。在最详细的视图,可以看到哪一行被测试和哪一行没被测试,使用一个有用的颜色编码文件视图。

如果想指定一个特定的Jacoco版本,简单地在build type中添加一个Jacoco配置块,定义版本:

jacoco {
    toolVersion = "0.7.1.201405082137"
}

然而,明确的定义版本不是必须的;不论如何Jacoco将会工作。

总结

本章节中,我们了解了测试Android app和library的几种选项。我们开始着手简单地单元测试,然后了解了更多使用Robolectric的Android特定的测试。然后我们涉及了功能测试并从Espresso开始。最后,我们了解了使能测试覆盖率报告去查看测试套件在哪续被提升。既然知道了如何使用gradle和AS运行整个测试套件,可以生成覆盖率报告,没有理由不写测试的。在第八章我们将会了解更多使用持续集成工具的自动化测试的方式。

下一章节包含了自定义build进程最重要的一个方面:创建自定义task和插件。这一章节也包含了对Groovy的简单介绍。这不仅当创建task和插件时有帮助,也使得理解gradle如何工作更容易。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值