美文网首页Gradle For Android
Gradle For Android(6)--测试单元

Gradle For Android(6)--测试单元

作者: None_Ling | 来源:发表于2018-10-20 22:58 被阅读2次

    介绍

    为了保证APP的质量,有一些自动化测试也是很重要的。很长一段时间Android Developement Tools缺少了对自动化测试的支持。但是最近Google让开发者们可以更容易的接入这些测试了。

    很多旧的Framework已经升级,而新的Framework也可以保证我们可以在APP和Library中访问这些。我们不仅仅可以在Android Studio中执行这些测试任务,也可以在命令行中执行,比如说通过Gradle。

    Unit tests

    一个写的好的单元测试不仅仅能够保证质量,它也能让我们检查新的代码是不是会破坏原有的功能性代码。Android Studio和Gradle Android Plugin可以为单元测试提供支持,但是需要我们可以配置一些东西。

    JUnit

    JUnit是一个常用的单元测试Lib。它可以让写出来的单元测试很容易的理解。值得注意的是,这些特殊的单元测试只对业务逻辑测试有用,而与Android SDK相关的则不会生效。

    在使用JUnit写单元测试之前,你需要创建一个为了tests的目录。这个目录可以叫做test,并且它应该和你的main目录同级。这个目录结构如下:

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

    你可以创建一个测试的Class在src/test/java/com.example.app
    为了使用最新的JUnit,可以使用JUnit版本4,在test构建中添加如下依赖关系:

    dependencies {
           testCompile 'junit:junit:4.12'
    }
    

    值得注意的是,我们使用testCompile,而不是compile。使用testCompile会保证只有在tests中该依赖才会被构建进去,而其他的版本则不会。在Dependencies中加入testCompile不会在Release的APK中编译,如果需要在一些特殊的BuildType或者ProductFlavors中加入配置,那么可以使用test-only的依赖来指定构建。

    例如,如果你希望在付费版本加入JUnit测试,可以添加如下代码块:

    dependencies {
           testPaidCompile 'junit:junit:4.12'
    }
    

    当所有的事情都设置好了后,就是时候开始写一些单元测试了。以下为一个添加两个数字的test函数。

       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 test来执行所有的单元测试。如果你希望在一个Build Variant中来执行这些测试,那么可以添加这个Variant的名字即可。如果只想在Debug版本进行测试,那么就可以执行gradlewtestDebug。如果单元测试失败了,那么Gradle就会在命令行打印出来失败日志。如果成功了,那么Gradle会打印出来BUILD SUCCESSFUL的日志。

    如果某个test任务失败了,整个过程会立刻终止。也就意味着如果失败,所有的任务都不会执行。如果希望整个test流程都执行完的话,那么可以使用continue的Flag:

    $ gradlew test --continue
    

    我们也可以通过在一个正确的路径保存一个Test的类来在某个版本中执行Test任务。例如:如果我们希望在付费版本中测试特定的功能,则将该类文件放入src/testPaid/java/com.example.app目录下。

    如果你不想执行整个测试流程,而只是执行一个特定的测试类,你可以使用test标志位:

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

    执行测试任务不仅仅只会执行Test,也会创建一个Test Report,而这个文件的路径就放在app/build/reports/tests/debug/index.html。这个Report可以帮助我们查看哪儿失败了,并且对于自动化测试非常有用。Gradle会为每一个Build Variant执行测试任务构建一个Report。

    如果test任务执行成功,那么单元测试的报告就会如下:


    Unit Test

    我们可以直接使用Android Studio执行Test任务。当我们使用的时候,会在IDE中直接反馈,当任务失败的时候,则会出现错误码,如果任务成功的话,那么Run Tool Window会如下所示:


    Run Tool Window

    如果你想测试部分引用了Android特殊的类和资源的代码的话,那么普通的单元测试则不能使用。当执行这任务的时候,会出现java.lang.RuntimeException: Stub!错误。为了修复这个错误,我们需要手动实现每个Android SDK的方法,或者使用mocking框架。

    幸运的是,一部分Lib已经处理好了Android SDK的问题。Robolectric这个Lib提供了一个Android功能测试的快捷的方式,并且不需要设备和模拟器。

    Robolectric

    我们可以使用Robolectric来编写使用Android SDK和资源的测试。而这些测试任务会跑在一个JVM中。这也就意味着它不需要在设备或者虚拟机上使用Android资源了。因此,这样也会对于APP或者Library的UI组件表现的测试会更加快速。

    开始使用Robolectric之前,我们需要添加一些测试的Dependencies。在Robolectric之内,也需要包含JUnit,并且如果需要使用Support Library的话,你也需要使用Robolectricshadow-support类:

    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和兼容包中都有一些已知的问题。如果在执行的时候遇到缺失兼容包中的资源的话,可以通过下面的方式修复:
    在Module中加入一个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找到Support中的资源

    Functional tests

    功能测试用来测试App中的一些组件是否与预期一样进行工作的。例如,你可以创建一个功能性的测试:点击一个Button打开一个新的Activity。Android提供了一些功能性测试的框架,但是最简单的还是使用Espresso框架。

    Espresso

    Espresso Library通过Android Support仓库提供。所以可以通过SDK Manager安装。为了在设备上进行测试,我们需要定义一个test runner。通过testing support library,Google提供了一个名为AndroidJUnitRunner的test runner,它可以帮我们在Android设备上运行JUnit Test类。Test Runner会将App的Apk和test的APK安装到该设备上,并且执行所有的test,然后将test结果生成到report中。

    以下是如何设置test 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'
       }
    

    我们需要引用test 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不能完成构建,因为有多个相同的文件。幸运的是,它只是一个License描述,所以我们可以在构建中忽略它。这个错误包含了我们应该怎么做,我们可以在build.gradle中配置该选项:

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

    一旦build.gradle文件配置完成后,就可以开始添加测试单元了。功能测试和常规的单元测试不同,它存放于一个其他的目录。就像依赖配置一样,我们需要使用androidTest取代test,所以正确的功能测试目录为src/androidTest/java/com.example.app

    例如,这个测试类检查是否这个TextView中的Text是否在MainActivity中:

    @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测试之前,需要确保有一个设备或者模拟器连上了。如果没有连接设备执行该任务的话,则会报错:

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

    一旦连接了设备后,就可以通过gradlew connectedCheck来运行测试任务。这个任务会和connectedAndroidTest任务一起执行,在设备上执行Debug Build中的所有测试任务,并且创建DebugCoverageReport的报告。这个报告会在App目录下的build/outputs/ reports/androidTests/connected路径中。打开index.html来查看这个报告:

    Report

    功能测试报告会展示Device和Android的版本。你可以同时在多个设备上执行这些测试任务,所以这些设备信息会更好的查找到设备或者版本单独的Bug。

    如果你希望通过Android Studio来获取测试反馈,可以通过IDE直接在run/denig的配置中设置。Android Studio ToolBar上有一个Configuration选项:

    Edit Configuration

    我们可以在Edit Configurations中设置一个新的Configuration,并且创建一个新的Android测试配置。选择Module并且指定instrumentation runner为AndroidJUnitRunner,如下图所示:

    Espresso Configuration

    一旦保存了配置后,就可以点击Run启动测试任务。

    Test coverage

    一旦你开始了Android Project的测试任务,它可以很方便的知道代码被多少测试单元覆盖。Jacoco是最受欢迎的测试工具。

    Jacoco

    覆盖率报告是否生效是非常容易的,只需要在Build Type中设置testCoverageEnabled = true即可。例如:

    buildTypes {
         debug {
             testCoverageEnabled = true
         }
    }
    

    testCoverageEnabled打开时,执行gradlew connectedCheck就会生成覆盖率报告。而生成这个报告的任务名为createDebugCoverageReport。即使它没有在文档中记录,并且也没有在task列表中,而当你执行gradlew tasks时,它就会直接运行的。

    然而,由于createCoverageReport依赖于connectedCheck,你不能单独运行这几个任务。connectedCheck这个任务也需要链接一个模拟器或者设备才能执行,并且生成test coverage report。

    当这个任务被执行后,可以在app/build/outputs/reports/coverage/debug/index.html中找到覆盖率的报告。每一个Build Variant都有自己的覆盖率报告路径,因为每个Variant都有自己不同的tests。覆盖率测试报告如下:

    Report

    如果希望指定一个特殊的版本,那么在Build Type的配置代码块中加入Jacoco的版本定义:

    jacoco {
         toolVersion = "0.7.1.201405082137"
    }
    

    然而,Jacoco不需要显式的指定一个版本,Jacoco也可以工作。

    相关文章

      网友评论

        本文标题:Gradle For Android(6)--测试单元

        本文链接:https://www.haomeiwen.com/subject/kwwpzftx.html