Android测试 ---- Espresso + Jacoco

google官网上为开发者们介绍了Espresso测试框架,在之前的文章中已经讲到,该文章主要讲利用Espresso框架时如何获得测试代码覆盖率。

写了个例子在Github上:

git clone https://github.com/LxxCaroline/EspressoJacocoSample.git

在工程的目录如下:

project: EspressoJacocoSample

--module: app

--module: mylibrary

在该工程中有两个模块,app和mylibrary,app依赖mylibrary,我在app下面写了测试代码,想要测测试代码对于mylibrary的覆盖率。

先看下app中build.gradle的配置

apply plugin: 'com.android.application'
apply plugin:'jacoco'

jacoco{
    toolVersion "0.7.4.201502262128"
}

android {
    compileSdkVersion 15
    buildToolsVersion "22.0.1"

    defaultConfig {
        applicationId "com.example.hzlinxuanxuan.espressojacocosample"
        minSdkVersion 9
        targetSdkVersion 15
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "com.example.hzlinxuanxuan.espressojacocosample.JUnitJacocoTestRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        debug{
            testCoverageEnabled true
        }
    }
    packagingOptions {
        exclude 'LICENSE.txt'
    }
}

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    debugCompile project(path: ':mylibrary', configuration: 'debug')
    releaseCompile project(path: ':mylibrary', configuration: 'release')
    // Testing-only dependencies
    androidTestCompile 'com.android.support.test:runner:0.3'
    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
    compile files('libs/junit-4.12.jar')
}

task jacocoTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
    group="Reporting"
    description = "Generate Jacoco coverage reports"

    //exclude auto-generated classes and tests
    def fileFilter=['**/R.class',
                    '**/R$*.class',
                    '**/Manifest*.*',
                    '**/BuildConfig.*',
                    'android/**/*.*',
    ]
    def debugTree=fileTree(dir:
            "${rootDir}/mylibrary/build/intermediates/classes/debug",
            excludes: fileFilter)
    def sdkSrc="${rootDir}/mylibrary/src/main/java"

    //指明对哪个目录下的代码进行绘制覆盖率统计图标
    sourceDirectories=files([sdkSrc])
    //指明对哪个目录下的代码进行覆盖率统计
    classDirectories=files([debugTree])
    additionalSourceDirs=files([
            "${buildDir}/generated/source/buildConfig/debug",
            "${buildDir}/generated/source/r/debug"
    ])
    executionData=fileTree(dir:project.projectDir,includes:['**/*.exec','**/*.ec'])
    reports{
        xml.enabled=true
        xml.destination="${buildDir}/jacocoTestReport.xml"
        csv.enabled=false
        html.enabled=true
        html.destination="${buildDir}/reports/jacoco"
    }
}
其中以下脚本是配置espresso

androidTestCompile 'com.android.support.test:runner:0.3'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'
其中以下脚本配置jacoco(生成测试覆盖率的工具)

apply plugin:'jacoco'

jacoco{
    toolVersion "0.7.4.201502262128"
}

task jacocoTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){
    group="Reporting"
    description = "Generate Jacoco coverage reports"

    //exclude auto-generated classes and tests
    def fileFilter=['**/R.class',
                    '**/R$*.class',
                    '**/Manifest*.*',
                    '**/BuildConfig.*',
                    'android/**/*.*',
    ]
    def debugTree=fileTree(dir:
            "${rootDir}/mylibrary/build/intermediates/classes/debug",
            excludes: fileFilter)
    def sdkSrc="${rootDir}/mylibrary/src/main/java"

    //指明对哪个目录下的代码进行绘制覆盖率统计图标
    sourceDirectories=files([sdkSrc])
    //指明对哪个目录下的代码进行覆盖率统计
    classDirectories=files([debugTree])
    additionalSourceDirs=files([
            "${buildDir}/generated/source/buildConfig/debug",
            "${buildDir}/generated/source/r/debug"
    ])
    executionData=fileTree(dir:project.projectDir,includes:['**/*.exec','**/*.ec'])
    reports{
        xml.enabled=true
        xml.destination="${buildDir}/jacocoTestReport.xml"
        csv.enabled=false
        html.enabled=true
        html.destination="${buildDir}/reports/jacoco"
    }
}
当然还有一句重要的话,这句话是指明我们要使用的自定义的runner

testInstrumentationRunner "com.example.hzlinxuanxuan.espressojacocosample.JUnitJacocoTestRunner"

在上面脚本中编写了一个task(如果不理解task可以看该篇文章Android--Gradle的理解),执行该task就可以执行测试用例,并得到结果。

接下来是编写测试代码和自定义runner,在该目录下创建


EspressoTest.java中为测试代码

@RunWith(AndroidJUnit4.class)
@LargeTest
public class EspressoTest {

    @Rule
    public ActivityTestRule<MainActivity> actvRule = new ActivityTestRule(MainActivity.class);

    @Test
    public void testCase1() {
        onView(withId(R.id.btn)).perform(click());
    }
}
JUnitJacocoTestRunner.java:

public class JUnitJacocoTestRunner extends AndroidJUnitRunner {

    static {
        final String path = "/data/data/" + BuildConfig.APPLICATION_ID + "/coverage.ec";
        System.setProperty("jacoco-agent.destfile", path);
    }

    @Override
    public void finish(int resultCode, Bundle results) {
        try {
            Class rt = Class.forName("org.jacoco.agent.rt.RT");
            Method getAgent = rt.getMethod("getAgent");
            Method dump = getAgent.getReturnType().getMethod("dump", boolean.class);
            Object agent = getAgent.invoke(null);
            dump.invoke(agent, false);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        super.finish(resultCode,results);
    }
}

在app/src/...../MainActivity.java中的代码

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void click(View view){
        LogUtil.d("there is a message");
    }
}
在该该段代码中使用mylibrary模块中LogUtil的函数。

接下来看下mylibrary的build.gradle:

apply plugin: 'com.android.library'

android {
    publishNonDefault true
    compileSdkVersion 23
    buildToolsVersion "22.0.1"

    defaultConfig {
        minSdkVersion 9
        targetSdkVersion 15
    }
    buildTypes {

        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }

        //需要在这里也添加,否则计算出来的覆盖率为0
        debug{
            testCoverageEnabled true
        }
    }
}

dependencies {
    compile 'com.android.support:support-v4:19.+'
}

testCoverageEnabled true
该句代码说明允许对该代码进行统计覆盖率,非常重要。

在最开始的时候我们讲到了一个task,执行该task可以执行测试用例,可以从该处找到task

Android测试 ---- Espresso + Jacoco_第1张图片

如果一开始点开gradle,没有显示任何task的话,请点击左上方的刷新按钮,然后选择jacocoTestReport,双击就会执行测试用例。

要注意的是,如果有一个测试用例没有通过,则不能生成覆盖率的报告。

因为初始需求是测试mylibrary中的代码,所以在编写task的时候指定了使用哪个目录下

    def debugTree=fileTree(dir:
            "${rootDir}/mylibrary/build/intermediates/classes/debug",
            excludes: fileFilter)
    def sdkSrc="${rootDir}/mylibrary/src/main/java"

    //指明对哪个目录下的代码进行绘制覆盖率统计图标
    sourceDirectories=files([sdkSrc])
    //指明对哪个目录下的代码进行覆盖率统计
    classDirectories=files([debugTree])
如果读者想要测试app下的代码,只需要改成如下即可

    def debugTree=fileTree(dir:
            "${rootDir}/app/build/intermediates/classes/debug",
            excludes: fileFilter)
    def sdkSrc="${rootDir}/app/src/main/java"

    //指明对哪个目录下的代码进行绘制覆盖率统计图标
    sourceDirectories=files([sdkSrc])
    //指明对哪个目录下的代码进行覆盖率统计
    classDirectories=files([debugTree])

看到了么,只用修改目录路径即可。

生成测试报告之后去该目录下找

app\build\reports\jacoco

结果如下



打开index.html结果如下

Android测试 ---- Espresso + Jacoco_第2张图片

当然你可以看到哪些代码被覆盖了


Android测试 ---- Espresso + Jacoco_第3张图片

你可能感兴趣的:(Android测试 ---- Espresso + Jacoco)