以下为 HarmonyOS 5应用代码覆盖率统计的完整ArkTS实现方案,包含覆盖率插桩、数据收集和LCOV报告生成的代码示例:
1. 覆盖率统计架构
2. 覆盖率插桩配置
2.1 编译时插桩
// build-profile.json5
{
"coverage": {
"instrumentation": {
"includes": ["src/main/ets/**/*.ets"],
"excludes": ["**/test/**", "**/mock/**"]
},
"outputDir": "coverage"
}
}
2.2 运行时配置
// coverage-setup.ets
import { Coverage } from '@ohos.coverage';
export function setupCoverage() {
Coverage.start({
output: 'lcov',
watermarks: {
lines: [60, 80], // 低/高水位线
functions: [50, 70],
branches: [40, 60]
}
});
process.on('exit', () => {
Coverage.stop();
Coverage.generateReport();
});
}
3. 数据收集与合并
3.1 单次运行数据收集
// coverage-collector.ets
export async function collectCoverage() {
const coverageData = Coverage.getCurrentCoverage();
await writeFile(
`coverage/coverage-${Date.now()}.json`,
JSON.stringify(coverageData)
);
return coverageData;
}
3.2 多设备数据合并
// coverage-merger.ets
export function mergeCoverage(reports: CoverageReport[]) {
return reports.reduce((merged, report) => {
Object.entries(report).forEach(([file, data]) => {
if (!merged[file]) {
merged[file] = { ...data };
return;
}
// 合并行覆盖率
Object.entries(data.lines).forEach(([line, hits]) => {
merged[file].lines[line] = (merged[file].lines[line] || 0) + hits;
});
// 合并分支覆盖率
Object.entries(data.branches).forEach(([branch, info]) => {
if (!merged[file].branches[branch]) {
merged[file].branches[branch] = { ...info };
} else {
merged[file].branches[branch].taken += info.taken;
}
});
});
return merged;
}, {} as CoverageReport);
}
4. LCOV报告生成
4.1 LCOV格式转换
// lcov-generator.ets
export function generateLCOV(data: CoverageReport) {
let lcov = '';
Object.entries(data).forEach(([file, coverage]) => {
lcov += `SF:${file}\n`;
// 行覆盖率
Object.entries(coverage.lines).forEach(([line, hits]) => {
lcov += `DA:${line},${hits}\n`;
});
// 分支覆盖率
Object.entries(coverage.branches).forEach(([branch, info]) => {
lcov += `BRDA:${branch},${info.taken},${info.total}\n`;
});
// 方法覆盖率
coverage.functions.forEach(fn => {
lcov += `FN:${fn.line},${fn.name}\n`;
lcov += `FNDA:${fn.hits},${fn.name}\n`;
});
lcov += `LF:${coverage.lines.total}\n`;
lcov += `LH:${coverage.lines.covered}\n`;
lcov += `BRF:${coverage.branches.total}\n`;
lcov += `BRH:${coverage.branches.covered}\n`;
lcov += 'end_of_record\n';
});
return lcov;
}
4.2 生成HTML报告
// html-reporter.ets
import { LCOVParser } from '@ohos.coverage';
export async function generateHTMLReport() {
const lcovData = await readFile('coverage/lcov.info');
const coverage = LCOVParser.parse(lcovData);
return new ReportBuilder()
.addSummary(coverage.summary)
.addFileDetails(coverage.files)
.addTrendChart(getHistory())
.generate('coverage-report.html');
}
5. 覆盖率阈值检查
5.1 质量门禁配置
// quality-gate.ets
export function checkCoverageThresholds(coverage: CoverageSummary) {
const thresholds = {
lines: 80,
functions: 70,
branches: 60
};
const violations = [];
Object.entries(thresholds).forEach(([type, threshold]) => {
if (coverage[type] < threshold) {
violations.push({
metric: type,
actual: coverage[type],
expected: threshold
});
}
});
return {
passed: violations.length === 0,
violations
};
}
5.2 CI集成检查
// ci-check.ets
import { generateLCOV, checkCoverageThresholds } from './coverage';
export async function enforceCoverage() {
const lcov = await generateLCOV();
const result = checkCoverageThresholds(lcov.summary);
if (!result.passed) {
console.error('覆盖率未达标:');
result.violations.forEach(v => {
console.error(`${v.metric}: ${v.actual}% < ${v.expected}%`);
});
process.exit(1);
}
}
6. 高级功能扩展
6.1 增量覆盖率统计
// diff-coverage.ets
import { GitDiff } from '@ohos/git';
export async function getDiffCoverage() {
const changedFiles = await GitDiff.getChangedFiles();
const coverage = await parseLCOV();
return {
files: coverage.files.filter(file =>
changedFiles.includes(file.path)
),
summary: calculateDiffSummary(changedFiles, coverage)
};
}
6.2 历史趋势跟踪
// coverage-history.ets
import { Database } from '@ohos.sqlite';
export async function saveDailyCoverage(coverage: CoverageSummary) {
const db = new Database('coverage.db');
await db.run(
`INSERT INTO coverage_history
(date, lines, functions, branches)
VALUES (?, ?, ?, ?)`,
[new Date(), coverage.lines, coverage.functions, coverage.branches]
);
}
7. 完整工作流示例
7.1 测试覆盖率收集
// test-workflow.ets
import { setupCoverage, collectCoverage } from './coverage';
describe('业务逻辑测试', () => {
beforeAll(() => setupCoverage());
it('应覆盖核心流程', () => {
// 测试代码...
});
afterAll(async () => {
await collectCoverage();
});
});
7.2 生成最终报告
// report-workflow.ets
import { mergeCoverage, generateLCOV } from './coverage';
async function main() {
const reports = await loadAllCoverage();
const merged = mergeCoverage(reports);
const lcov = generateLCOV(merged);
await writeFile('coverage/lcov.info', lcov);
await generateHTMLReport();
}
main().catch(console.error);
8. 关键指标说明
| 指标 | 计算公式 | 达标建议 |
|---|---|---|
| 行覆盖率 | 覆盖行数/总行数×100% | ≥80% |
| 分支覆盖率 | 覆盖分支/总分支×100% | ≥60% |
| 方法覆盖率 | 覆盖方法/总方法×100% | ≥70% |
| 变更覆盖率 | 修改代码的覆盖率 | ≥90% |
9. 常见问题解决
| 问题现象 | 解决方案 | 代码调整示例 |
|---|---|---|
| 第三方库代码被统计 | 排除node_modules目录 | excludes: ["**/node_modules/**"] |
| 插桩后性能下降 | 启用采样模式 | sampling: 0.1 |
| 合并报告数据冲突 | 使用checksum校验文件 | fileChecksum: true |
| 历史趋势显示异常 | 定期清理数据库 | Database.cleanOldData(30) |
10. 可视化报告示例
// custom-report.ets
import { SunburstChart } from '@ohos.viz';
export function renderSunburst(coverage: CoverageByFolder) {
return new SunburstChart({
data: Object.entries(coverage).map(([folder, metrics]) => ({
name: folder,
children: [
{ name: '行覆盖率', value: metrics.lines },
{ name: '分支覆盖率', value: metrics.branches }
]
})),
colorScale: ['#FF5252', '#FFC107', '#4CAF50']
}).render('sunburst.html');
}
通过本方案可实现:
- 精准 行/分支/方法覆盖率统计
- 多维度 数据聚合分析
- 自动化 质量门禁
- 可视化 历史趋势追踪