代码覆盖率统计:为HarmonyOS5应用生成LCOV报告

118 阅读2分钟

以下为 ​​HarmonyOS 5应用代码覆盖率统计的完整ArkTS实现方案​​,包含覆盖率插桩、数据收集和LCOV报告生成的代码示例:


1. 覆盖率统计架构

image.png


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');
}

通过本方案可实现:

  1. ​精准​​ 行/分支/方法覆盖率统计
  2. ​多维度​​ 数据聚合分析
  3. ​自动化​​ 质量门禁
  4. ​可视化​​ 历史趋势追踪