Gradle for Android(六) 运行测试

为了确保app或者library的质量,自动化测试非常重要。很长一段时间,ADT缺少对自动化测试的支持,但是最近,Google花费了很大的努力帮助开发者进行测试。一些老的框架进行了升级,并添加了一些新的框架,以确保我们可以彻底测试app和library。我们不仅可以在Android Studio中运行它们,也可以使用Gradle在命令行运行。

本章,我们会探索测试Android app和library的几种方式。我们还将研究Gradle如何帮助实现自动化测试。

本章包含如下内容:

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

单元测试

好的单元测试不仅能保证工程质量,也可以用来检测新的代码是否会破坏原有功能。Android Studio和Gradle Android插件对单元测试提供了本地支持,你只需要进行少量配置就可以使用。

JUnit

JUnit是一个非常流行的单元测试库,已经有了十多年的历史。它使测试变得简单易懂。需要记住的是,这些流行的单元测试仅对测试业务逻辑有用,不能测试Android SDK相关的代码。

在写测试之前,你需要创建一个目录。按照惯例,目录名称为test,和main目录在同一级别。目录结构如下:

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

随后你可以在src/test/java/com.example.app目录添加测试类。

为了使用JUnit的最新特性,建议使用JUnit4.0以上版本。添加如下依赖:

dependencies {
    testCompile 'junit:junit:4.12'
}

注意我们此处使用testCompile,而不是compile,这可以确保只在运行测试时构建此依赖,正式打包时不会。

如果你的某个构建类型或者product flavors有一些特性,你也可以为这个特定的构建单独添加测试依赖。比如,你只想为付费版添加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));
    }
}

要运行所有级别的测试,只需执行gradlew测试。如果你只想在某个构建变体运行测试,只需要添加上这个变体的名字。比如,执行gradlewtestDebug将只在debug变体上运行测试。如果测试失败,Gradle会在命令行中打印错误信息。如果运行成功,Gradle会显示BUILD SUCCESSFUL

单行测试失败将导致真个测试任务失败。这意味着并不是所有的测试都会执行。如果你希望这个测试单元在所有的构建变体上都被执行,添加continue标识:

$ gradlew test --continue

你也可以仅为某个构建变体编写测试,只需要将测试类放在相应的目录就可以了。比如,想要测试付费版本的话,可以将测试类放在src/testPaid/java/com.example.app中。

如果你不想运行整个测试单元,只想测试某个类,可以添加标识:

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

执行测试不仅会运行所有的测试,还会生成一个测试报告,路径为app/build/reports/tests/debug/index.html。测试报告可以帮助排查问题,在自动测试中非常有用。Gradle会为每个构建变体生成一个测试报告。

你也可以在Android Studio中运行测试。在IDE中可以得到及时反馈,并且可以点击失败的测试,从而快速定位到问题代码。

如果你想测试包含Android特有类或者资源引用的代码,常规单元测试是行不通的,这会引发java.lang.RuntimeException: Stub!错误。为了解决这个问题,你可以自己实现Android SDK的每个方法,或者使用一个模拟框架。幸运的是,一些库解决了这个问题,其中最流行的是Robolectric,它提供了简单的方式来测试Android功能,并且不需要设备或者模拟器。

Robolectric

使用Robolectric,你可以编写使用了Android SDK和资源的测试,并在JVM中运行它。也就是说,你不需要设备或者模拟器就可以测试UI组件。

为了使用Robolectric,你需要添加一些依赖。除了Robolectric,你也需要JUnit。如果你使用了Android support library,你还需要Robolectric shadow。

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的文本在点击按钮后发生了变化:

@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 L系统和suport library上存在一些问题。如果你碰到无法找到资源和兼容库相关的错误,可以尝试在模块中添加一个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找到兼容库资源。

功能测试

功能测试用于测试应用的几个模块是否如预期那样正常工作。比如,你可以创建一个功能测试,用于确认点击一个按钮是否会打开一个新的页面。Android有几个功能测试框架,最简单易用的是Espresso。

Espresso

Google创建了Espresso,使开发者更容易编写功能测试。它在Android support repository中提供,你可以使用SDK Manager安装。

为了在设备上运行测试,你需要有一个测试运行器。在testing support library中,Google提供了AndroidJUnitRunner测试运行器,它可以帮助你在Android设备上运行JUnit测试类。测试运行器会在设备上安装应用APK和测试APK,运行所有测试,然后根据结果创建测试报告。

假如你已经下载了testing support library,可以如下配置测试运行器:

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'
}

你需要引用testing support library和espresso-core才能使用Espresso。最后一个依赖,espresso-contrib,是Espresso的一些补充特性,但不是核心库的一部分。

注意这些依赖使用的是androidTestCompile配置,而不是testCompile。这是为了区分单元测试和功能测试。

如果这个时候尝试运行测试,你会得到下面的错误:

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将不能完成构建。幸运的是,它仅是一个许可描述,所以我们可以在构建中移除。这个错误本身也包含如何修复的信息:

You can ignore those files in your build.gradle:

android {
    packagingOptions {
        exclude 'LICENSE.txt'
    }
}

配置好构建文件之后,你就可以添加测试了。功能测试和常规的单元测试放在不同的目录下。正如依赖配置一样,你需要使用androidTest代替test,正确的功能测试目录是src/androidTest/java/com.example.app。下面是一个测试类的例子,用于检测MainActivityTextView中的文本是否正确。

@RunWith(AndroidJUnit4.class)
@SmallTest
public class TestingEspressoMainActivityTest {
    @Rule
    public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class);

    @Test
    public void testHelloWorldIsShown() {
        onView(withText("Hello world!")).check(matches(isDisplayed()));
    }
}

在运行Espresso测试之前,你需要确保有一个设备或者模拟器。如果你忘记连接设备,运行测试任务会引发如下异常:

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

当你连接上设备或者启动了模拟器之后,你可以使用gradlew connectedCheck来运行Espresso测试。这个任务会执行connectedAndroidTestcreateDebugCoverageReport,前者会在所有连接的设备上为debug变体运行所有测试,后者会生成一个测试报告。

你可以在应用的build/outputs/reports/androidTests/connected目录找到生成的测试报告。

功能测试报告会显示运行测试的设备以及Android版本。你可以同时在多个设备上运行测试,所以设备信息可以很容易的查看特定设备或者特定版本的问题。

如果你想在Android Studio中得到测试的反馈,你需要设置run/debug配置。run/debug配置代表一组run/debug启动属性。Android Studio的工具栏有一个配置选择器,你可以选择想要使用的run/debug配置。

图1 Android Studio run/debug配置选择器

你可以点击Edit Configurations…打开配置编辑器,创建一个新的Android测试配置。选择测试模块,设备运行器设置为AndroidJUnitRunner

Gradle for Android(六) 运行测试_第1张图片
图2 编辑配置窗口

将新配置保存后,你就可以在配置选择器中选择它,然后点击Run按钮来运行所有测试。

在Android Studio中运行Espresso测试有一个问题:不会生成测试报告。这是因为Android Studio执行connectedAndroidTest任务,而不是connectedCheck任务,而测试报告是由connectedCheck生成的。

测试覆盖率

为Android工程编写测试后,需要知道测试在代码库中的覆盖率。Java有很多测试覆盖率工具,最流行的是Jacoco,它也是默认支持的,很容易上手。

Jacoco

启用覆盖率报告非常简单。你只需要在测试的构建类型中设置testCoverageEnabled = true

buildTypes {
    debug {
        testCoverageEnabled = true
    }
}

启用测试覆盖率后,执行gradlew connectedCheck会生成覆盖率报告。真正生成报告的是createDebugCoverageReport。即使它没有被列入文档,执行gradlew tasks时也不会出现在任务列表中,但你可以直接运行它。而由于createCoverageReport依赖connectedCheck,所以你不可以单独运行它们。依赖connectedCheck也表明你需要连接设备或者模拟器来生成测试覆盖率报告。

执行任务之后,你可以在app/build/outputs/reports/coverage/debug/index.html路径找到覆盖率报告。每个构建变体有各自的目录。

如果你想设置一个特定版本的Jacoco,只需:

jacoco {
    toolVersion = "0.7.1.201405082137"
}

你可能感兴趣的:(Gradle for Android(六) 运行测试)