修改配置文件后不会触发增量编译,需要重新编译插桩再测试。
DevEco Studio支持黑盒覆盖率测试,不需要开发测试用例,将编译插桩的HAP包推到设备上,然后对该应用/元服务模拟用户操作,测试完成后可生成覆盖率报告,当前仅支持Stage模型。
将设备与电脑进行连接,并对应用/元服务签名,具体请参考使用本地真机运行应用和应用/元服务签名。
从DevEco Studio 5.1.0 Release版本开始,支持自定义参与覆盖率测试的文件。在工程目录下创建coverage-filter.json5文件,在文件中配置相关字段,DevEco Studio编译插桩时将按照coverage-filter.json5文件中的配置进行过滤。

修改配置文件后不会触发增量编译,需要重新编译插桩再测试。
coverage-filter.json5文件支持以下字段。
字段名称 | 可选/必选 | 类型 | 适用的覆盖率测试 | 含义 |
|---|---|---|---|---|
include | 可选 | 字符串数组 | 黑盒覆盖率测试 仪器覆盖率测试 本地覆盖率测试 | 配置参与覆盖率测试的文件或文件夹路径。
说明 include的优先级比exclude高。 |
exclude | 可选 | 字符串数组 | 黑盒覆盖率测试 仪器覆盖率测试 本地覆盖率测试 | 配置不参与覆盖率测试的文件或文件夹路径。
|
includeHar | 可选 | 字符串数组 | 黑盒覆盖率测试 仪器覆盖率测试 本地覆盖率测试 | 配置参与覆盖率测试的远程源码har包,不支持远程字节码har包,不支持通配符。 从26.0.0版本开始支持。 |
extraAbilities | 可选 | 对象数组 | 黑盒覆盖率测试 | 如果module.json5中配置的ability是通过import外部的ability类实现,同时在当前文件通过export导出,此ability无法生成黑盒覆盖率数据。可配置extraAbilities>path字段,填写外部的ability路径,生成黑盒覆盖率数据。支持相对路径,不支持通配符。示例如下。 从26.0.0版本开始支持。 |
extraAbilities字段示例:
- // xxx.ets
- import EntryAbility from './EntryAbility';
- export default EntryAbility;
-
- // coverage-filter.json5
- {
- "extraAbilities": [
- {
- "path": "entry/src/main/ets/entryability/EntryAbility.ets", // import的EntryAbility的相对路径
- },
- ],
- }
- // 26.0.0及以上版本
- {
- "include":[
- "entry/src/main/ets/pages" // 无通配符,包括pages下所有文件
- ],
- "exclude":[
- "**/src/**/HspTest?.ets", // src目录下的HspTest1.ets、HspTest2.ets等文件
- "**/src/**/*Test1.*", // src目录下所有文件名中有Test1.的文件
- "**/src/**/utils" // src目录底下所有utils文件夹里的文件
- ],
- "includeHar":[
- "math"
- ],
- "extraAbilities": [
- {
- "path": "entry/src/main/ets/entryability/EntryAbility.ets"
- }
- ]
- }
- // 26.0.0以下版本
- {
- "include":[
- "entry/src/main/ets/pages/aaa.ets"
- ],
- "exclude":[
- "entry/src/main/ets/pages"
- ]
- }
有两种方式进行编译与安装,DevEco Studio方式和命令行方式,具体步骤如下。

- hvigorw --mode module -p module={moduleName@targetName} -p product={productName} -p buildMode=test -p ohos-test-coverage=true -p coverage-mode=black assembleHap --parallel --incremental --daemon
如果HAP依赖HSP,需要单独编译HSP,将以上命令的assembleHap替换为assembleHsp即可。
- hdc uninstall {bundleName}
- hdc install {SignedHapPath}
- hdc shell aa dump -c -l
- const context = this.getUIContext().getHostContext() as common.UIAbilityContext;
- context.eventHub.emit('coverage');
从API 13开始,如果用户使用最近任务列表一键清理来关闭应用,将不会执行onDestroy()回调,导致获取不到覆盖率数据。
- // 如果是应用则执行该命令,其中LocalPath非必填,如果不填写,默认存放在当前执行命令的目录
- hdc file recv data/app/el2/100/base/{bundleName}/haps/{moduleName}/cache {LocalPath}
- // 如果是元服务则执行该命令,其中LocalPath必填
- hdc file recv -b {bundleName} ls ./data/storage/el2/base/haps/{moduleName}/cache {LocalPath}
在多模块相互跳转的场景下,只需要取最后退出的模块下生成的覆盖率数据json文件,但特殊场景下如多模块无跳转关系,则需要取每个独立模块下生成的覆盖率数据json文件。
- hvigorw collectCoverage -p projectPath={projectPath} -p reportPath={reportPath} -p coverageFile={projectPath}/{moduleName}/.test/default/intermediates/ohosTest/init_coverage.json#{LocalPath/bjc_cov_yyyyMMdd_HHmmss_SSS.json} -p patch={patchPath}
在多模块相互跳转的场景下,需要取各模块的init_coverage.json文件路径,与bjc_cov_yyyyMMdd_HHmmss_SSS.json文件通过#拼接生成coverageFile参数。

执行覆盖率测试后,会生成两份报告,一份是html格式,用于可视化查看报告,一份是json格式,即coverageReport.json文件,记录了详细的覆盖率数据,文件中各字段的含义请参考覆盖率coverageReport.json文件。
测试覆盖率报告有三个测量维度,分别是:

以下是关于三个测量维度的细节说明:
常见的流程控制语句有if、while、do...while、switch、for等等,以及三目运算符(condition ? exprIfTrue : exprIfFalse),需要确保流程控制的每个边界情况(即分支)都被执行。
箭头函数在方法内时,可以正常统计覆盖率,如果作为参数声明,则无法统计该行覆盖率。
示例如下:
- import { window } from '@kit.ArkUI'; // +0 方法外不统计
- let filePath :string; // +0 方法外不统计
- const fileName = 'a.txt'; // +0 方法外不统计
-
- export function doTheThing () // +1
- { // +1
- let s1: string; // +1
- const str = 'aaa'; // +1
- console.log(str); // +1
- } // +0
-
- export class Person { // +0 方法外不统计
- name: string = '' // +0 方法外不统计
- constructor (n:string) { // +1 构造函数
- this.name = n; // +1
- } // +0
-
- static sayHello () { // +1 类静态方法
- console.log('hello'); // +1
- } // +0
-
- walk () { // +1 类实例方法
- for ( // +1
- let i=0; // +1
- i < 10; // +1
- i++) // +1
- { // +1
- } // +0
- } // +0
- } // +0
-
- function func ():object { // +1
- return Object({ // +1 一个语句被拆分为多行
- a: 1, // +1
- b: 2, // +1
- }) // +0
- } // +0
-
- func(); // +0 方法外不统计
-
- function foo(n:number, m:number){} // +1
-
- function bar():number { // +1
- return 1; // +1
- }
-
- foo(1, bar()); // +0 方法外不统计

代码中的某些分支可能很难、甚至无法测试,DevEco Studio提供了instrument ignore * 语法来进行忽略,使得某些代码不计入覆盖率。
使用时需先清除缓存,点击菜单栏Build -> Clean Project。
- import {testA} from './Index'
- // instrument ignore file 忽略整个文件
-
- // instrument ignore next 忽略代码块
- export function sum(a:number,b:number){
- return a+b;
- }
- sum(1,2);
-
- let a = 1;
- // instrument ignore else 忽略else分支
- if (a!=1) {
- // do something
- console.log('BBB');
- }else {
- console.log('AAA');
- }
-
- // instrument ignore if 忽略if分支
- if (a==1) {
- // do something
- console.log('BBB');
- }else {
- console.log('AAA');
- }
覆盖率coverageReport.json文件记录了详细的覆盖率数据,文件中各字段的含义如下。
在阅读本文前,请先查看覆盖率报告解读,了解行覆盖率、分支覆盖率和函数覆盖率的相关概念和统计方式。
- {
- "summary": {
- "lines": { // 行数总览
- "total": 43, // 可执行行代码行数
- "covered": 12, // 覆盖数量
- "pct": 27.91 // 行覆盖率
- },
- "functions": { // 函数总览
- "total": 17, // 函数数量
- "covered": 4, // 覆盖数量
- "pct": 23.53 // 函数覆盖率
- },
- "branches": { // 分支总览
- "total": 2, // 分支数量
- "covered": 0, // 覆盖数量
- "pct": 0 // 分支覆盖率
- }
- },
- }
- {
- "files": [
- {
- "version": "bjc v1.0.0", // 覆盖率算法版本
- "versionCode": 10000, // 覆盖率算法版本代码
- "path": "D:/DevEcoStudioProjects/MyApplication36/application/src/main/ets/applicationability/ApplicationAbility.ets", // 文件路径
- "hash": "6828362e96a78934b93db4b980fa5ad83af85a111bf187e74da89ae0c0ec613a", // 文件内容hash值
- "lineCnt": 44, // 当前文件总行数
- "count": 0, // 执行次数
- "projectPath": "D:/DevEcoStudioProjects/MyApplication36", // 工程路径
- "functions": [], // 函数集合
- "exeLine": {}, // 可执行代码行
- "summary": {} // 单个文件的覆盖率详情
- },
- ]
- }
functions是个数组,记录了文件中所有函数的详细覆盖率数据,数组中的每个元素对应一个函数。
regions是一个可执行行数组,数组可能有一个元素、两个元素或多个元素。
第一个元素是该方法对应的代码区域,如果不止一个元素,后面的元素是方法内的可执行代码区域。元素中每个字段的含义如下。
- "regions": [
- {
- "startLoc": { // 开始代码位置
- "line": 8, // 起始行号
- "col": 3 // 起始列号
- },
- "endLoc": { // 结束代码位置
- "line": 10, // 结束行号
- "col": 4 // 结束列号
- },
- "count": 0, // 执行次数
- "ignored": 0 // 忽略次数
- }
- ]

- {
- "name": "dddd",
- "count": 1,
- "regions": [
- {
- "startLoc": {
- "line": 31,
- "col": 1
- },
- "endLoc": {
- "line": 33,
- "col": 2
- },
- "count": 1,
- "ignored": 0
- }
- ],
- },

- {
- "name": "aaaaa",
- "count": 2,
- "regions": [
- { // 方法对应的代码区域
- "startLoc": {
- "line": 2,
- "col": 1
- },
- "endLoc": {
- "line": 9,
- "col": 2
- },
- "count": 2,
- "ignored": 0
- },
- { // 可执行代码区域
- "startLoc": {
- "line": 4,
- "col": 3
- },
- "endLoc": {
- "line": 9,
- "col": 2
- },
- "count": 2,
- "ignored": 0
- }
- ],
- }

- {
- "name": "bbb",
- "count": 1,
- "regions": [
- { // 方法对应的代码区域11-18行
- "startLoc": {
- "line": 11,
- "col": 1
- },
- "endLoc": {
- "line": 18,
- "col": 2
- },
- "count": 1,
- "ignored": 0
- },
- { // 第一个可执行代码区域13-15行
- "startLoc": {
- "line": 13,
- "col": 13
- },
- "endLoc": {
- "line": 15,
- "col": 4
- },
- "count": 0, // 由于flag是false,代码未执行
- "ignored": 0
- },
- { // 第二个可执行代码区域15-17行
- "startLoc": {
- "line": 15,
- "col": 10
- },
- "endLoc": {
- "line": 17,
- "col": 4
- },
- "count": 1, // 代码被执行
- "ignored": 0
- }
- ],
- }
branches是个分支数组,会将if和switch case这种条件判断语句相关的代码块放入数组中,数组中每个元素的字段含义如下。
- "branches": [
- {
- "startLoc": { // 开始代码位置
- "line": 46, // 起始行号
- "col": 10 // 起始列号
- },
- "endLoc": { // 结束代码位置
- "line": 46, // 结束行号
- "col": 11 // 结束列号
- },
- "trueCount": 0, // 该行满足条件的已执行次数,0表示未执行
- "falseCount": 1, // 该行不满足条件的已执行次数,0表示未执行
- "group": [ // 分组,if语句不涉及分组,switch case涉及分组
- 0, // group:[0,1],表示branches数组的0号和1号元素属于一个switch case
- 1
- ],
- "ignored": 0 // 忽略次数
- }
- ]
示例一:调用eeee(2)。

- {
- "name": "eeee",
- "count": 1,
- "regions": [
- // ...
- ],
- "branches": [
- {
- "startLoc": {
- "line": 46,
- "col": 10
- },
- "endLoc": {
- "line": 46,
- "col": 11
- },
- "trueCount": 0, // 该行条件未执行
- "falseCount": 1, // 该行已执行,但不满足条件
- "group": [ // 0和1号元素属于1个switch case,2和3号元素属于另一个switch case
- 0,
- 1
- ],
- "ignored": 0
- },
- {
- "startLoc": {
- "line": 49,
- "col": 10
- },
- "endLoc": {
- "line": 49,
- "col": 11
- },
- "trueCount": 1,
- "falseCount": 0,
- "group": [
- 0,
- 1
- ],
- "ignored": 0
- },
- {
- "startLoc": {
- "line": 55,
- "col": 10
- },
- "endLoc": {
- "line": 55,
- "col": 13
- },
- "trueCount": 0,
- "falseCount": 1,
- "group": [
- 2,
- 3
- ],
- "ignored": 0
- },
- {
- "startLoc": {
- "line": 58,
- "col": 10
- },
- "endLoc": {
- "line": 58,
- "col": 13
- },
- "trueCount": 1,
- "falseCount": 0,
- "group": [
- 2,
- 3
- ],
- "ignored": 0
- }
- ],
- }
示例二:调用bbb(2)和bbb(-1),该方法触发两次。

branches的0号元素,对应12行,trueCount和falseCount都为1,表示该行触发了两次,一次满足条件,一次不满条件。
- {
- "name": "bbb",
- "count": 2,
- "regions": [
- // ...
- ],
- "branches": [
- {
- "startLoc": {
- "line": 12,
- "col": 7
- },
- "endLoc": {
- "line": 12,
- "col": 12
- },
- "trueCount": 1,
- "falseCount": 1,
- "group": [],
- "ignored": 0
- },
- {
- "startLoc": {
- "line": 14,
- "col": 14
- },
- "endLoc": {
- "line": 14,
- "col": 20
- },
- "trueCount": 0,
- "falseCount": 1,
- "group": [],
- "ignored": 0
- },
- {
- "startLoc": {
- "line": 22,
- "col": 7
- },
- "endLoc": {
- "line": 22,
- "col": 13
- },
- "trueCount": 1,
- "falseCount": 1,
- "group": [],
- "ignored": 0
- }
- ]
- }
exeLine记录了所有可执行行的行号,示例如下。

生成的exeLine为:
- "exeLine": {
- "0": 2,
- "1": 3,
- "2": 4,
- "3": 6,
- "4": 7,
- "5": 8,
- }
summary记录了单个文件的覆盖率详情。
- "summary": {
- "lines": { // 行数总览
- "total": 10, // 可执行代码行数
- "covered": 5, // 覆盖数量
- "pct": 50, // 行覆盖率
- "executedLineCount": [ // 代码行执行次数,-1表示该行不被统计,0表示未执行,1-N表示执行1-N次
- -1,
- -1,
- -1,
- 0,
- -1,
- 1,
- 0,
- 2,
- 2,
- -1,
- 1,
- 1,
- 0,
- 0,
- 0,
- -1
- ]
- },
- "functions": { // 函数总览
- "total": 6, // 函数数量
- "covered": 5, // 覆盖数量
- "pct": 83.33 // 函数覆盖率
- },
- "branches": { // 分支总览
- "total": 2, // 分支数量
- "covered": 1, // 覆盖数量
- "pct": 50 // 分支覆盖率
- }
- }