当前位置: 首页 > news >正文

Android IOS 代码覆盖率实现

背景

新功能测试以及回归测试在手工测试的情况下,即便用例再为详尽,也会存在遗漏的用例。通过统计手工测试覆盖率的数据,可以及时的完善用例。本文以android jacoco及IOS Xcode 实现代码覆盖率方法。

Android 端实现

引入依赖

在app 目录下的build.gradle 引入jacoco 包

   implementation 'org.jacoco:org.jacoco.core:0.8.7'//导入jacoco的版本包

相关类代码

FinishListener:

package 你的包名;
public interface FinishListener {void onActivityFinished();void dumpIntermediateCoverage(String filePath);
}

InstrumentedActivity

package 包名;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.util.Log;public class InstrumentedActivity extends MainActivity{public static String TAG = "IntrumentedActivity";private FinishListener mListener;public void setFinishListener(FinishListener listener) {mListener = listener;}@Overridepublic void onDestroy() {super.onDestroy();//Log.d(TAG + ".com.example.coveragetest.InstrumentedActivity", "onDestroy()");super.finish();if (mListener != null) {mListener.onActivityFinished();}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);}
}

JacocoInstrumentation

package 包名;import android.app.Activity;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Bundle;
import android.os.Looper;
import android.util.Log;import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;public class JacocoInstrumentation extends Instrumentation implementsFinishListener {public static String TAG = "JacocoInstrumentation:";private static String DEFAULT_COVERAGE_FILE_PATH = "/mnt/sdcard/coverage.ec";private final Bundle mResults = new Bundle();private Intent mIntent;private static final boolean LOGD = true;private boolean mCoverage = true;private String mCoverageFilePath;/*** Constructor*/public JacocoInstrumentation() {}@Overridepublic void onCreate(Bundle arguments) {Log.d(TAG, "onCreate(" + arguments + ")");super.onCreate(arguments);DEFAULT_COVERAGE_FILE_PATH = getContext().getFilesDir().getPath().toString() + "/coverage.ec";Log.d(TAG, "DEFAULT_COVERAGE_FILE_PATH is : "+DEFAULT_COVERAGE_FILE_PATH);File file = new File(DEFAULT_COVERAGE_FILE_PATH);if (!file.exists()) {try {file.createNewFile();} catch (IOException e) {Log.d(TAG, "异常 : " + e);e.printStackTrace();}}if (arguments != null) {mCoverageFilePath = arguments.getString("coverageFile");}mIntent = new Intent(getTargetContext(), InstrumentedActivity.class);mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);start();}@Overridepublic void onStart() {if (LOGD)Log.d(TAG, "onStart()");super.onStart();Looper.prepare();InstrumentedActivity activity = (InstrumentedActivity) startActivitySync(mIntent);activity.setFinishListener(this);}private void generateCoverageReport() {Log.d(TAG, "generateCoverageReport():" + getCoverageFilePath());OutputStream out = null;try {out = new FileOutputStream(getCoverageFilePath(), 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();}}}}private String getCoverageFilePath() {if (mCoverageFilePath == null) {return DEFAULT_COVERAGE_FILE_PATH;} else {return mCoverageFilePath;}}private boolean setCoverageFilePath(String filePath){if(filePath != null && filePath.length() > 0) {mCoverageFilePath = filePath;return true;}return false;}@Overridepublic void onActivityFinished() {if (LOGD)Log.d(TAG, "onActivityFinished()");if (mCoverage) {generateCoverageReport();}finish(Activity.RESULT_OK, mResults);}@Overridepublic void dumpIntermediateCoverage(String filePath){// TODO Auto-generated method stubif(LOGD){Log.d(TAG,"Intermidate Dump Called with file name :"+ filePath);}if(mCoverage){if(!setCoverageFilePath(filePath)){if(LOGD){Log.d(TAG,"Unable to set the given file path:"+filePath+" as dump target.");}}generateCoverageReport();setCoverageFilePath(DEFAULT_COVERAGE_FILE_PATH);}}}

修改 build.gradle 文件

  • 增加jacoco插件,打开覆盖率开关

app目录下创建jacoco.gradle 如下:

apply plugin: 'jacoco'jacoco {toolVersion = "0.8.2"
}
def coverageSourceDirs = ['../app/src/main/java'
]def coverageClassDirs = ['../app/build/intermediates/javac/debug/classes'
]task jacocoTestReport(type: JacocoReport) {group = "Reporting"description = "Generate Jacoco coverage reports after running tests."reports {xml.enabled = truehtml.enabled = true}classDirectories.setFrom(files(files(coverageClassDirs).files.collect {fileTree(dir: it,// 过滤不需要统计的class文件excludes: ['**/R*.class','**/*$InjectAdapter.class','**/*$ModuleAdapter.class','**/*$ViewInjector*.class'])}))sourceDirectories.setFrom(files(coverageSourceDirs))executionData.setFrom(files("$buildDir/outputs/code-coverage/coverage.ec"))doFirst {new File("$buildDir/intermediates/javac/debug/classes/").eachFileRecurse { file ->if (file.name.contains('$$')) {file.renameTo(file.path.replace('$$', '$'))}}}
}

build.gradle 引入 jacoco 插件

 apply from:'jacoco.gradle'

修改 AndroidManifest.xml

添加instrumentation 声明

  <instrumentationandroid:handleProfiling="true"android:label="CoverageInstrumentation"android:name="com.example.coveragetest.JacocoInstrumentation"android:targetPackage="com.example.coveragetest"/>

测试步骤及报告生成

  1. 通过 adb shell am instrument 包名/包名.test.JacocoInstrumentation 启动 app;
  2. 进行 app 手工测试,测试完成后退出 App,覆盖率文件会保存在手机/data/data/yourPackageName/files/coverage.ec 目录
  3. 导出 coverage.ec 至  $buildDir/outputs/code-coverage 目录下;
  4. 使用 gradle jacocoTestReport 命令行分析覆盖率文件并生成 html 报告;
  5. 查看覆盖率报告

报告目录结构

测试结果如下图

PS:

  • 绿色:表示行覆盖充分。
  • 红色:表示未覆盖的行。
  • 空白色:代表方法未修改,无需覆盖。
  • 黄色棱形:表示分支覆盖不全。
  • 绿色棱形:表示分支覆盖完全。

ios Xcode 实现

基本配置

  • 配置targets如下图

  • 配置test如下图

运行测试及查看覆盖率

  • 运行测试,点击test

  • 查看覆盖率

http://www.sczhlp.com/news/5312/

相关文章:

  • HomeAssistant 接入国家电网
  • 干货 | 读懂 Appium 日志,让测试效率翻倍!
  • 2025年8月4日
  • VMware安装WIN10系统时报错EFI Network 解决方法
  • 友链
  • OpenCV实战之人脸美颜美型算法
  • 振荡器闪烁相噪学习
  • Mac 禁用 SIP
  • windows11 重启开机后无法找到wifi
  • 自定义我的博客园
  • 封装usehooks监听本地缓存事件触发问题
  • 利用GeminiBalance搭建Gemini API号池
  • linux source命令使用详细介绍 - 教程
  • RNDIS技术解析:USB上网的极速通道
  • kgdb调试
  • 性能调优:troubleshooting slow parse sql on 19.16
  • CF1385F Removing Leaves
  • Markdown入门
  • 当首页没有权限不跳进404页面, 跳进接口菜单list返回的第一个
  • 中电金信多项产品入选信通院“数字金融”产品目录
  • Prometheus+node_exporter+Grafana安装教程
  • 读书笔记:数据库并发控制与多版本机制解析
  • 洛谷题单指南-状态压缩动态规划-P1879 [USACO06NOV] Corn Fields G
  • throw 和 catch 关键字的作用
  • python分割长文本
  • Delphi中检测并记录TClientDataSet字段变更的技术实现
  • 3Ds Max 2019 安装使用全流程指南(图文讲解 | 含语言设置与授权配置)
  • markdown
  • Easysearch 集成阿里云与 Ollama Embedding API,构建端到端的语义搜索系统
  • eureka服务实例多节点容器部署