一、项目使用覆盖率初衷
由于公司开发项目业务类型是给企业做定制项目,开发周期短。开发人员在开发过程中往往在将项目提交给测试组测试时,没有进行全面项目测试,导致后面测试组测试出来一堆bug问题,甚至中断测试流程。为了解决大量开发和测试人员的时间,以及检测项目测试的代码是否测试覆盖的程度。在这种条件下,决定使用代码覆盖率来进行查看控制。
二、覆盖率概念
代码覆盖率分析实际上一种度量方式,间接度量质量的方法的过程,是在保证测试质量的时候潜在保证实际产品的质量,在程序中寻找没有被测试用例测试过的地方的流程,创建新的测试用例来增加覆盖率的流程。
代码覆盖分析是一种结构测试技术,属于白盒测试的范畴,结构化测试是以源代码的意图表现为依据来比较被测试程序行为的,与以需求规格为依据去比较被测程序行为的功能测试形成对比,结构化测试检查程序是如何工作的,以及代码结构和逻辑方面的潜在缺陷,而功能测试是不管程序内部是如何运作的,它只检查以及关心程序实现了什么。另外代码覆盖有时被称为“覆盖分析”、“测试覆盖分析”、“测试覆盖”、“覆盖监视器”等。
三、JaCoCo
JaCoCo简述
JaCoCo是一个开源的覆盖率工具(官网地址: http://www.eclemma.org/JaCoCo/ ),它针对的开发语言是java,其使用方法很灵活,可以嵌入到Ant、Maven中;可以作为Eclipse插件,可以使用其JavaAgent技术监控Java程序等等。很多第三方的工具提供了对JaCoCo的集成,如sonar、Jenkins等。
JaCoCo包含了多种尺度的覆盖率计数器,包含指令级覆盖(Instructions,C0coverage),分支(Branches,C1coverage)、圈复杂度(CyclomaticComplexity)、行覆盖(Lines)、方法覆盖(non-abstract methods)、类覆盖(classes),这里就详细介绍不属于入门使用范畴。
四、工程实现覆盖率
在app目录的gradle文件
apply plugin: 'com.android.application'
apply plugin: 'jacoco'//添加插件jacoco
jacoco {
toolVersion = "0.7.9"//声明jacoco的版本号
}
android {
compileSdkVersion
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.weex.jasso"
minSdkVersion
targetSdkVersion
versionCode
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
debug {
testCoverageEnabled = true//设置为true
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.1.1'
compile 'org.jacoco:org.jacoco.core:0.7.9'//导入jacoco的版本包
compile 'com.android.support.constraint:constraint-layout:+'
}
def coverageSourceDirs = [
'../app/src/main/java'
]
//建立task任务将outputs/code-coverage/connected/目录下coverage.ec文件的生成覆盖率报告
task jacocoTestReport(type: JacocoReport) {
group = "Reporting"
description = "Generate Jacoco coverage reports after running tests."
reports {
xml.enabled = true
html.enabled = true
}
classDirectories = fileTree(
dir: './build/intermediates/classes/debug',
excludes: ['**/R*.class',
'**/*$InjectAdapter.class',
'**/*$ModuleAdapter.class',
'**/*$ViewInjector*.class'
])
sourceDirectories = files(coverageSourceDirs)
executionData = files("$buildDir/outputs/code-coverage/connected/coverage.ec")
doFirst {
new File("$buildDir/intermediates/classes/").eachFileRecurse { file ->
if (file.name.contains('$$')) {
file.renameTo(file.path.replace('$$', '$'))
}
}
}
}
生成代码覆盖率的文件
1. 建立基础类BaseActivity
public abstract class BaseActivity extends Activity {
public static String DEFAULT_COVERAGE_FILE_PATH = Environment.getExternalStorageDirectory()+"/hb/oe/";
public static String TAG=Test1Activity.class.getName();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
createFile(DEFAULT_COVERAGE_FILE_PATH,"coverage.ec");
}
@Override
protected void onDestroy() {
super.onDestroy();
closeFile();
}
public static void createFile(String path,String fileName){
File file =null;
if (path==null){
new Exception("path no null");
}
if (fileName==null){
file = new File(path);
}else {
file=new File(path,fileName);
}
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
public void closeFile(){
OutputStream out = null;
try {
out = new FileOutputStream(DEFAULT_COVERAGE_FILE_PATH+"/coverage.ec", false);
Object agent = Class.forName("org.jacoco.agent.rt.RT")
.getMethod("getAgent")
.invoke(null);
out.write((byte[]) agent.getClass().getMethod("getExecutionData", boolean.class)
.invoke(agent, false));
} catch (Exception e) {
Log.d(TAG, e.toString(), e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
基础类在oncreate()创建coverage.ec文件用于写入代码覆盖率及方法createFile()方法,在界面关闭的onDestroy()生命周期写入覆盖数据方法closeFile();
2. 其他界面的activity继承基础类BaseActivity
这里我为了演示写了Demo,写了两个类Test1Activity 和test2Activity
Test1Activity .class
public class Test1Activity extends BaseActivity {
private Button mToastBtn;
private Button mRequireBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void require(){
Toast.makeText(this,"网络请求",Toast.LENGTH_LONG).show();
}
public void toast(){
Intent intent=new Intent(this,Test2Activity.class);
startActivity(intent);
}
public void showTips(View view){
Toast.makeText(this, "我是提示信息", Toast.LENGTH_SHORT).show();
}
public void showError(View view){
Toast.makeText(this,"我正在抛出错误",Toast.LENGTH_SHORT).show();
startActivity(new Intent(this,Test2Activity.class));
finish();
}
public void showTest(){
Toast.makeText(this,"测试",Toast.LENGTH_SHORT).show();
}
}
Test2Activity.class
public class Test2Activity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
}
}
在Test1Activity写了两个按钮点击事件,用于明确区别事件点击覆盖率;
3. 直接app运行到手机端
4. 生成报告
将手机中的coverage.ec文件(上述代码有具体路径,可以自行日记打印出来,沿着相对路径去找)复制到build/outputs/code-coverage/connected下
5.生成报表的gradle指令
gradlew jacocoTestReport
还记得在gradle创建的task任务叫jacocoTestReport,就是将coverage.ec生成报表文件可视化。
五、报告分析
报表在build/reports/jacoco/jacocoTestReport/html下,右击查找index.html点击打开。
如上图所示,标记绿色的为分支覆盖充分,标记黄色的为部分分支覆盖(上图没有),标红色的为未执行该分支;
Demo的下载
小红的测试生涯 http://www.dzwanli.com.cn/?p=1374
使用 Jacoco 实现 Android 端手工测试覆盖率统计 https://testerhome.com/topics/8554