(4.5.6.1)Android 代码覆盖率工具使用进阶

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

写了个例子在Github上:

[plain]  view plain  copy
  1. git clone https://github.com/LxxCaroline/EspressoJacocoSample.git  

在工程的目录如下:

project: EspressoJacocoSample

--module: app

--module: mylibrary

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

先看下app中build.gradle的配置

[plain]  view plain  copy
  1. apply plugin: 'com.android.application'  
  2. apply plugin:'jacoco'  
  3.   
  4. jacoco{  
  5.     toolVersion "0.7.4.201502262128"  
  6. }  
  7.   
  8. android {  
  9.     compileSdkVersion 15  
  10.     buildToolsVersion "22.0.1"  
  11.   
  12.     defaultConfig {  
  13.         applicationId "com.example.hzlinxuanxuan.espressojacocosample"  
  14.         minSdkVersion 9  
  15.         targetSdkVersion 15  
  16.         versionCode 1  
  17.         versionName "1.0"  
  18.         testInstrumentationRunner "com.example.hzlinxuanxuan.espressojacocosample.JUnitJacocoTestRunner"  
  19.     }  
  20.     buildTypes {  
  21.         release {  
  22.             minifyEnabled false  
  23.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  24.         }  
  25.   
  26.         debug{  
  27.             testCoverageEnabled true  
  28.         }  
  29.     }  
  30.     packagingOptions {  
  31.         exclude 'LICENSE.txt'  
  32.     }  
  33. }  
  34.   
  35. dependencies {  
  36.     compile fileTree(include: ['*.jar'], dir: 'libs')  
  37.     debugCompile project(path: ':mylibrary', configuration: 'debug')  
  38.     releaseCompile project(path: ':mylibrary', configuration: 'release')  
  39.     // Testing-only dependencies  
  40.     androidTestCompile 'com.android.support.test:runner:0.3'  
  41.     androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2'  
  42.     compile files('libs/junit-4.12.jar')  
  43. }  
  44.   
  45. task jacocoTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){  
  46.     group="Reporting"  
  47.     description = "Generate Jacoco coverage reports"  
  48.   
  49.     //exclude auto-generated classes and tests  
  50.     def fileFilter=['**/R.class',  
  51.                     '**/R$*.class',  
  52.                     '**/Manifest*.*',  
  53.                     '**/BuildConfig.*',  
  54.                     'android/**/*.*',  
  55.     ]  
  56.     def debugTree=fileTree(dir:  
  57.             "${rootDir}/mylibrary/build/intermediates/classes/debug",  
  58.             excludes: fileFilter)  
  59.     def sdkSrc="${rootDir}/mylibrary/src/main/java"  
  60.   
  61.     //指明对哪个目录下的代码进行绘制覆盖率统计图标  
  62.     sourceDirectories=files([sdkSrc])  
  63.     //指明对哪个目录下的代码进行覆盖率统计  
  64.     classDirectories=files([debugTree])  
  65.     additionalSourceDirs=files([  
  66.             "${buildDir}/generated/source/buildConfig/debug",  
  67.             "${buildDir}/generated/source/r/debug"  
  68.     ])  
  69.     executionData=fileTree(dir:project.projectDir,includes:['**/*.exec','**/*.ec'])  
  70.     reports{  
  71.         xml.enabled=true  
  72.         xml.destination="${buildDir}/jacocoTestReport.xml"  
  73.         csv.enabled=false  
  74.         html.enabled=true  
  75.         html.destination="${buildDir}/reports/jacoco"  
  76.     }  
  77. }  
其中以下脚本是配置espresso

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

[plain]  view plain  copy
  1. apply plugin:'jacoco'  
  2.   
  3. jacoco{  
  4.     toolVersion "0.7.4.201502262128"  
  5. }  
  6.   
  7. task jacocoTestReport(type:JacocoReport,dependsOn:"connectedAndroidTest"){  
  8.     group="Reporting"  
  9.     description = "Generate Jacoco coverage reports"  
  10.   
  11.     //exclude auto-generated classes and tests  
  12.     def fileFilter=['**/R.class',  
  13.                     '**/R$*.class',  
  14.                     '**/Manifest*.*',  
  15.                     '**/BuildConfig.*',  
  16.                     'android/**/*.*',  
  17.     ]  
  18.     def debugTree=fileTree(dir:  
  19.             "${rootDir}/mylibrary/build/intermediates/classes/debug",  
  20.             excludes: fileFilter)  
  21.     def sdkSrc="${rootDir}/mylibrary/src/main/java"  
  22.   
  23.     //指明对哪个目录下的代码进行绘制覆盖率统计图标  
  24.     sourceDirectories=files([sdkSrc])  
  25.     //指明对哪个目录下的代码进行覆盖率统计  
  26.     classDirectories=files([debugTree])  
  27.     additionalSourceDirs=files([  
  28.             "${buildDir}/generated/source/buildConfig/debug",  
  29.             "${buildDir}/generated/source/r/debug"  
  30.     ])  
  31.     executionData=fileTree(dir:project.projectDir,includes:['**/*.exec','**/*.ec'])  
  32.     reports{  
  33.         xml.enabled=true  
  34.         xml.destination="${buildDir}/jacocoTestReport.xml"  
  35.         csv.enabled=false  
  36.         html.enabled=true  
  37.         html.destination="${buildDir}/reports/jacoco"  
  38.     }  
  39. }  
  • task的类型为JacocoReport,JacocoReport是Gradle内置的一个类型,该类型的task用于生成Jacoco覆盖率报告
  • dependsOn: "connectedAndroidTest":这段是表明该task需要在connectedAndroidTest task完成之后进行,connectedAndroidTest是系统内置的自动运行测试工程的task,会默认在连接到电脑的设备上(有且仅有1台,否则会报错)执行全部的测试方法,如果dependsOn的task执行失败了则不会执行我们定义的task。由于我们使用的是adb命令运行测试脚本,因此不要添加这部分,直接写成task jacocoTestReport(type: JacocoReport){}即可
  • reports代码块定义各种报告类型的开关,我们在这里开启了XML和HTML格式的报告输出
  • classDirectories代码块定义了生成报告使用的目标文件类,他的参数是一个FileCollection类型,我们可以使用FileTree来定义它,dir为目录名,includes后面为需要在报告中显示的文件,excludes为不需要在报告中显示的文件,如果不带includes及其参数会使用dir下的全部文件,否则需要按照其后的参数进行匹配仅使用符合匹配的文件,如果带excludes参数则会从已被选中的文件中在排除掉匹配其后参数的文件。<color red>目标目录需要使用编译后的class文件,即./build/intermediates/classes/debug下的文件,而不是JAVA源码文件</color>
  • executionData代码块定义了要被统计的覆盖率文件的路径,该路径下的全部文件都会被用于覆盖率的计算
  • 设置完成后运行该task即可生成Jacoco代码覆盖率报告,报告生成的路径为:./build/reports/jacoco/<task名>,其下有XML和HTML两份报告,HTML的报告长这样的:


当然还有一句重要的话,这句话是指明我们要使用的自定义的runner

[plain]  view plain  copy
  1. testInstrumentationRunner "com.example.hzlinxuanxuan.espressojacocosample.JUnitJacocoTestRunner"  

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

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


EspressoTest.Java中为测试代码

[java]  view plain  copy
  1. @RunWith(AndroidJUnit4.class)  
  2. @LargeTest  
  3. public class EspressoTest {  
  4.   
  5.     @Rule  
  6.     public ActivityTestRule<MainActivity> actvRule = new ActivityTestRule(MainActivity.class);  
  7.   
  8.     @Test  
  9.     public void testCase1() {  
  10.         onView(withId(R.id.btn)).perform(click());  
  11.     }  
  12. }  
JUnitJacocoTestRunner.java:

[java]  view plain  copy
  1. public class JUnitJacocoTestRunner extends AndroidJUnitRunner {  
  2.   
  3.     static {  
  4.         final String path = "/data/data/" + BuildConfig.APPLICATION_ID + "/coverage.ec";  
  5.         System.setProperty("jacoco-agent.destfile", path);  
  6.     }  
  7.   
  8.     @Override  
  9.     public void finish(int resultCode, Bundle results) {  
  10.         try {  
  11.             Class rt = Class.forName("org.jacoco.agent.rt.RT");  
  12.             Method getAgent = rt.getMethod("getAgent");  
  13.             Method dump = getAgent.getReturnType().getMethod("dump"boolean.class);  
  14.             Object agent = getAgent.invoke(null);  
  15.             dump.invoke(agent, false);  
  16.         } catch (ClassNotFoundException e) {  
  17.             e.printStackTrace();  
  18.         } catch (NoSuchMethodException e) {  
  19.             e.printStackTrace();  
  20.         } catch (InvocationTargetException e) {  
  21.             e.printStackTrace();  
  22.         } catch (IllegalAccessException e) {  
  23.             e.printStackTrace();  
  24.         }  
  25.         super.finish(resultCode,results);  
  26.     }  
  27. }  

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

[java]  view plain  copy
  1. public class MainActivity extends Activity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.     }  
  8.   
  9.     public void click(View view){  
  10.         LogUtil.d("there is a message");  
  11.     }  
  12. }  
在该该段代码中使用mylibrary模块中LogUtil的函数。

接下来看下mylibrary的build.gradle:

[plain]  view plain  copy
  1. apply plugin: 'com.android.library'  
  2.   
  3. android {  
  4.     publishNonDefault true  
  5.     compileSdkVersion 23  
  6.     buildToolsVersion "22.0.1"  
  7.   
  8.     defaultConfig {  
  9.         minSdkVersion 9  
  10.         targetSdkVersion 15  
  11.     }  
  12.     buildTypes {  
  13.   
  14.         release {  
  15.             minifyEnabled false  
  16.             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'  
  17.         }  
  18.   
  19.         //需要在这里也添加,否则计算出来的覆盖率为0  
  20.         debug{  
  21.             testCoverageEnabled true  
  22.         }  
  23.     }  
  24. }  
  25.   
  26. dependencies {  
  27.     compile 'com.android.support:support-v4:19.+'  
  28. }  

[plain]  view plain  copy
  1. testCoverageEnabled true  
该句代码说明允许对该代码进行统计覆盖率,非常重要。

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

(4.5.6.1)Android 代码覆盖率工具使用进阶_第1张图片

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

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

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

[plain]  view plain  copy
  1. def debugTree=fileTree(dir:  
  2.         "${rootDir}/mylibrary/build/intermediates/classes/debug",  
  3.         excludes: fileFilter)  
  4. def sdkSrc="${rootDir}/mylibrary/src/main/java"  
  5.   
  6. //指明对哪个目录下的代码进行绘制覆盖率统计图标  
  7. sourceDirectories=files([sdkSrc])  
  8. //指明对哪个目录下的代码进行覆盖率统计  
  9. classDirectories=files([debugTree])  
如果读者想要测试app下的代码,只需要改成如下即可

[plain]  view plain  copy
  1. def debugTree=fileTree(dir:  
  2.         "${rootDir}/app/build/intermediates/classes/debug",  
  3.         excludes: fileFilter)  
  4. def sdkSrc="${rootDir}/app/src/main/java"  
  5.   
  6. //指明对哪个目录下的代码进行绘制覆盖率统计图标  
  7. sourceDirectories=files([sdkSrc])  
  8. //指明对哪个目录下的代码进行覆盖率统计  
  9. classDirectories=files([debugTree])  

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

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

app\build\reports\jacoco

结果如下



打开index.html结果如下

(4.5.6.1)Android 代码覆盖率工具使用进阶_第2张图片

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


(4.5.6.1)Android 代码覆盖率工具使用进阶_第3张图片

你可能感兴趣的:((4.5.6.1)Android 代码覆盖率工具使用进阶)