HarmonyOS开发:测试覆盖率与代码覆盖分析

举报
Jack20 发表于 2026/06/24 15:17:11 2026/06/24
【摘要】 HarmonyOS开发:测试覆盖率与代码覆盖分析📌 核心要点:覆盖率不是目的,是手段——它告诉你哪些代码还没被测试"碰"过,哪些分支可能藏着Bug。搞懂行覆盖、分支覆盖、函数覆盖的区别,学会用DevEco Studio的覆盖率工具,才能让测试真正"覆盖"到关键路径。 一、背景与动机你写了50个测试用例,全绿,信心满满地提交了代码。但你有没有想过——这50个用例,到底测了多少代码?有没有某...

HarmonyOS开发:测试覆盖率与代码覆盖分析

📌 核心要点:覆盖率不是目的,是手段——它告诉你哪些代码还没被测试"碰"过,哪些分支可能藏着Bug。搞懂行覆盖、分支覆盖、函数覆盖的区别,学会用DevEco Studio的覆盖率工具,才能让测试真正"覆盖"到关键路径。


一、背景与动机

你写了50个测试用例,全绿,信心满满地提交了代码。但你有没有想过——这50个用例,到底测了多少代码?有没有某个关键分支,从来没被任何测试走到过?

没有覆盖率数据,你就像蒙着眼走路——感觉走了很远,实际可能一直在原地打转。

覆盖率解决的是一个"盲区"问题:让你看到哪些代码还没被测试覆盖。80%的覆盖率意味着还有20%的代码从未在测试中执行过,那些地方就是Bug的潜在藏身之处。

但覆盖率也不是万能的。100%的覆盖率不等于100%的正确性——你的测试可能走了每行代码,但断言写错了,照样漏Bug。覆盖率是必要条件,不是充分条件


二、核心原理

2.1 覆盖率类型体系

flowchart TB
    A[代码覆盖率] --> B[行覆盖率<br/>Line Coverage]
    A --> C[分支覆盖率<br/>Branch Coverage]
    A --> D[函数覆盖率<br/>Function Coverage]
    A --> E[语句覆盖率<br/>Statement Coverage]

    B --> B1[已执行行数 / 总行数]
    B --> B2[最基础最直观]
    B --> B3[可能遗漏分支]

    C --> C1[已执行分支数 / 总分支数]
    C --> C2[if/else/switch路径]
    C --> C3[发现隐藏Bug]

    D --> D1[已调用函数数 / 总函数数]
    D --> D2[粗粒度概览]
    D --> D3[可能遗漏内部逻辑]

    E --> E1[已执行语句数 / 总语句数]
    E --> E2[比行覆盖更精细]
    E --> E3[一行多语句场景]

    classDef mainStyle fill:#4CAF50,stroke:#388E3C,color:#fff,font-weight:bold
    classDef lineStyle fill:#2196F3,stroke:#1976D2,color:#fff
    classDef branchStyle fill:#F44336,stroke:#D32F2F,color:#fff
    classDef funcStyle fill:#FF9800,stroke:#F57C00,color:#fff
    classDef stmtStyle fill:#9C27B0,stroke:#7B1FA2,color:#fff

    class A mainStyle
    class B,B1,B2,B3 lineStyle
    class C,C1,C2,C3 branchStyle
    class D,D1,D2,D3 funcStyle
    class E,E1,E2,E3 stmtStyle

2.2 覆盖率类型详解

覆盖率类型 计算方式 示例 优缺点
行覆盖率 已执行行/总可执行行 10行代码跑了8行 = 80% 直观但可能遗漏分支
分支覆盖率 已执行分支/总分支 if-else走了if = 50% 最有价值,发现隐藏路径
函数覆盖率 已调用函数/总函数 5个函数调了4个 = 80% 粗粒度,快速定位未测函数
语句覆盖率 已执行语句/总语句 类似行覆盖但更精细 一行多语句时更准确

2.3 覆盖率的合理目标

覆盖率目标建议:
├── 核心业务逻辑:≥ 90%(分支覆盖率)
├── 工具类/通用组件:≥ 80%(行覆盖率)
├── UI/入口代码:≥ 60%(函数覆盖率)
├── 第三方适配层:≥ 50%(重点路径覆盖)
└── 追求100%?不推荐——边际收益递减,成本爆炸

三、代码实战

3.1 基础用法:理解覆盖率差异

先看一段代码,理解不同覆盖率类型的差异:

// GradeCalculator.ets - 成绩计算器
export class GradeCalculator {
  // 根据分数计算等级
  static getGrade(score: number): string {
    if (score < 0 || score > 100) {       // 分支1:非法分数
      throw new Error('分数必须在0-100之间')
    }
    if (score >= 90) {                     // 分支2:优秀
      return 'A'
    } else if (score >= 80) {              // 分支3:良好
      return 'B'
    } else if (score >= 70) {              // 分支4:中等
      return 'C'
    } else if (score >= 60) {              // 分支5:及格
      return 'D'
    } else {                               // 分支6:不及格
      return 'F'
    }
  }

  // 计算GPA
  static calculateGPA(grades: string[]): number {
    if (grades.length === 0) {             // 分支1:空数组
      return 0
    }

    let totalPoints = 0
    for (const grade of grades) {
      switch (grade) {
        case 'A': totalPoints += 4.0; break  // 分支2-7
        case 'B': totalPoints += 3.0; break
        case 'C': totalPoints += 2.0; break
        case 'D': totalPoints += 1.0; break
        case 'F': totalPoints += 0.0; break
        default: totalPoints += 0.0; break     // 分支8:非法等级
      }
    }

    return totalPoints / grades.length
  }

  // 判断是否通过
  static isPassing(score: number): boolean {
    return score >= 60
  }

  // 获取评语
  static getRemark(score: number): string {
    const grade = GradeCalculator.getGrade(score)
    const remarks: Record<string, string> = {
      'A': '非常优秀!',
      'B': '表现良好',
      'C': '还需努力',
      'D': '勉强及格',
      'F': '需要补考'
    }
    return remarks[grade] || '未知等级'
  }
}
// GradeCalculatorTest.ets - 覆盖率差异演示
import { describe, it } from '@ohos/hypium'
import { GradeCalculator } from '../../../main/ets/utils/GradeCalculator'

export default function gradeCalculatorTest() {
  describe('GradeCalculator成绩计算', () => {

    describe('getGrade等级计算', () => {
      // 只测了部分分支——分支覆盖率不全
      it('getGrade_90分_A', 0, () => {
        assertEqual(GradeCalculator.getGrade(90), 'A')
      })

      it('getGrade_50分_F', 0, () => {
        assertEqual(GradeCalculator.getGrade(50), 'F')
      })

      it('getGrade_75分_C', 0, () => {
        assertEqual(GradeCalculator.getGrade(75), 'C')
      })

      // 注意:B(80-89)和D(60-69)分支没有被测试覆盖
      // 注意:非法分数(<0或>100)分支也没有被测试覆盖

      // 行覆盖率:约70%(大部分行走了)
      // 分支覆盖率:约40%(6个分支只走了3个)
      // 函数覆盖率:100%(getGrade被调用了)
    })

    describe('calculateGPA计算GPA', () => {
      it('calculateGPA_正常成绩', 0, () => {
        const gpa = GradeCalculator.calculateGPA(['A', 'B', 'C'])
        assertClose(gpa, 3.0, 0.01)
      })

      it('calculateGPA_空数组_返回0', 0, () => {
        assertEqual(GradeCalculator.calculateGPA([]), 0)
      })

      // 注意:switch的default分支没有被覆盖
      // 注意:D和F的case分支没有被覆盖
    })

    describe('isPassing是否通过', () => {
      it('isPassing_60分_通过', 0, () => {
        assertEqual(GradeCalculator.isPassing(60), true)
      })

      it('isPassing_59分_不通过', 0, () => {
        assertEqual(GradeCalculator.isPassing(59), false)
      })
    })

    // 注意:getRemark函数完全没有测试——函数覆盖率只有75%
  })
}

3.2 进阶用法:提升覆盖率的完整测试

// GradeCalculatorFullTest.ets - 完整覆盖率测试
import { describe, it } from '@ohos/hypium'
import { GradeCalculator } from '../../../main/ets/utils/GradeCalculator'

export default function gradeCalculatorFullTest() {
  describe('GradeCalculator完整覆盖测试', () => {

    describe('getGrade全覆盖', () => {
      it('getGrade_非法负数_抛出异常', 0, () => {
        assertThrowError(() => GradeCalculator.getGrade(-1))
      })

      it('getGrade_非法超100_抛出异常', 0, () => {
        assertThrowError(() => GradeCalculator.getGrade(101))
      })

      it('getGrade_边界0_F', 0, () => {
        assertEqual(GradeCalculator.getGrade(0), 'F')
      })

      it('getGrade_59_F', 0, () => {
        assertEqual(GradeCalculator.getGrade(59), 'F')
      })

      it('getGrade_60_D', 0, () => {
        assertEqual(GradeCalculator.getGrade(60), 'D')
      })

      it('getGrade_69_D', 0, () => {
        assertEqual(GradeCalculator.getGrade(69), 'D')
      })

      it('getGrade_70_C', 0, () => {
        assertEqual(GradeCalculator.getGrade(70), 'C')
      })

      it('getGrade_79_C', 0, () => {
        assertEqual(GradeCalculator.getGrade(79), 'C')
      })

      it('getGrade_80_B', 0, () => {
        assertEqual(GradeCalculator.getGrade(80), 'B')
      })

      it('getGrade_89_B', 0, () => {
        assertEqual(GradeCalculator.getGrade(89), 'B')
      })

      it('getGrade_90_A', 0, () => {
        assertEqual(GradeCalculator.getGrade(90), 'A')
      })

      it('getGrade_100_A', 0, () => {
        assertEqual(GradeCalculator.getGrade(100), 'A')
      })

      // 分支覆盖率:100%(所有if/else分支都走了)
      // 行覆盖率:100%
    })

    describe('calculateGPA全覆盖', () => {
      it('calculateGPA_包含所有等级', 0, () => {
        const gpa = GradeCalculator.calculateGPA(['A', 'B', 'C', 'D', 'F'])
        assertClose(gpa, 2.0, 0.01)
      })

      it('calculateGPA_非法等级_default分支', 0, () => {
        const gpa = GradeCalculator.calculateGPA(['X'])  // 走default分支
        assertEqual(gpa, 0)
      })

      it('calculateGPA_全A_4.0', 0, () => {
        assertClose(GradeCalculator.calculateGPA(['A', 'A']), 4.0, 0.01)
      })

      it('calculateGPA_全F_0', 0, () => {
        assertClose(GradeCalculator.calculateGPA(['F', 'F']), 0, 0.01)
      })

      // 分支覆盖率:100%(switch所有case + default都走了)
    })

    describe('isPassing全覆盖', () => {
      it('isPassing_刚好60_通过', 0, () => {
        assertEqual(GradeCalculator.isPassing(60), true)
      })

      it('isPassing_59_不通过', 0, () => {
        assertEqual(GradeCalculator.isPassing(59), false)
      })

      it('isPassing_100_通过', 0, () => {
        assertEqual(GradeCalculator.isPassing(100), true)
      })

      it('isPassing_0_不通过', 0, () => {
        assertEqual(GradeCalculator.isPassing(0), false)
      })
    })

    describe('getRemark全覆盖', () => {
      it('getRemark_A_非常优秀', 0, () => {
        assertEqual(GradeCalculator.getRemark(95), '非常优秀!')
      })

      it('getRemark_B_表现良好', 0, () => {
        assertEqual(GradeCalculator.getRemark(85), '表现良好')
      })

      it('getRemark_C_还需努力', 0, () => {
        assertEqual(GradeCalculator.getRemark(75), '还需努力')
      })

      it('getRemark_D_勉强及格', 0, () => {
        assertEqual(GradeCalculator.getRemark(65), '勉强及格')
      })

      it('getRemark_F_需要补考', 0, () => {
        assertEqual(GradeCalculator.getRemark(30), '需要补考')
      })

      // 函数覆盖率:100%(所有函数都测了)
    })
  })
}

3.3 完整示例:覆盖率分析工具

// CoverageAnalyzer.ets - 覆盖率分析工具
export interface CoverageData {
  filePath: string
  lines: { total: number; covered: number; missed: number[] }
  branches: { total: number; covered: number; missed: string[] }
  functions: { total: number; covered: number; missed: string[] }
}

export interface CoverageSummary {
  lineCoverage: number
  branchCoverage: number
  functionCoverage: number
  totalFiles: number
  filesBelowThreshold: string[]
}

export class CoverageAnalyzer {
  private coverageData: CoverageData[] = []
  private thresholds = {
    line: 80,
    branch: 75,
    function: 90
  }

  // 添加文件覆盖率数据
  addFileCoverage(data: CoverageData): void {
    this.coverageData.push(data)
  }

  // 设置覆盖率阈值
  setThresholds(thresholds: { line?: number; branch?: number; function?: number }): void {
    if (thresholds.line !== undefined) this.thresholds.line = thresholds.line
    if (thresholds.branch !== undefined) this.thresholds.branch = thresholds.branch
    if (thresholds.function !== undefined) this.thresholds.function = thresholds.function
  }

  // 计算总体覆盖率
  getSummary(): CoverageSummary {
    let totalLines = 0, coveredLines = 0
    let totalBranches = 0, coveredBranches = 0
    let totalFunctions = 0, coveredFunctions = 0
    const filesBelowThreshold: string[] = []

    for (const data of this.coverageData) {
      totalLines += data.lines.total
      coveredLines += data.lines.covered
      totalBranches += data.branches.total
      coveredBranches += data.branches.covered
      totalFunctions += data.functions.total
      coveredFunctions += data.functions.covered

      // 检查是否低于阈值
      const lineCov = data.lines.total > 0 ? (data.lines.covered / data.lines.total) * 100 : 100
      if (lineCov < this.thresholds.line) {
        filesBelowThreshold.push(data.filePath)
      }
    }

    return {
      lineCoverage: totalLines > 0 ? Math.round((coveredLines / totalLines) * 10000) / 100 : 0,
      branchCoverage: totalBranches > 0 ? Math.round((coveredBranches / totalBranches) * 10000) / 100 : 0,
      functionCoverage: totalFunctions > 0 ? Math.round((coveredFunctions / totalFunctions) * 10000) / 100 : 0,
      totalFiles: this.coverageData.length,
      filesBelowThreshold
    }
  }

  // 获取未覆盖的行号
  getUncoveredLines(filePath: string): number[] {
    const data = this.coverageData.find(d => d.filePath === filePath)
    return data?.lines.missed ?? []
  }

  // 获取未覆盖的分支
  getUncoveredBranches(filePath: string): string[] {
    const data = this.coverageData.find(d => d.filePath === filePath)
    return data?.branches.missed ?? []
  }

  // 获取未覆盖的函数
  getUncoveredFunctions(filePath: string): string[] {
    const data = this.coverageData.find(d => d.filePath === filePath)
    return data?.functions.missed ?? []
  }

  // 生成覆盖率报告
  generateReport(): string {
    const summary = this.getSummary()
    const lines: string[] = []

    lines.push('========== 覆盖率报告 ==========')
    lines.push(`行覆盖率: ${summary.lineCoverage}% (阈值: ${this.thresholds.line}%)`)
    lines.push(`分支覆盖率: ${summary.branchCoverage}% (阈值: ${this.thresholds.branch}%)`)
    lines.push(`函数覆盖率: ${summary.functionCoverage}% (阈值: ${this.thresholds.function}%)`)
    lines.push(`文件总数: ${summary.totalFiles}`)

    if (summary.filesBelowThreshold.length > 0) {
      lines.push('')
      lines.push('⚠️ 低于阈值的文件:')
      for (const file of summary.filesBelowThreshold) {
        lines.push(`  - ${file}`)
      }
    }

    lines.push('')
    lines.push('---------- 文件详情 ----------')
    for (const data of this.coverageData) {
      const lineCov = data.lines.total > 0
        ? Math.round((data.lines.covered / data.lines.total) * 100)
        : 100
      const branchCov = data.branches.total > 0
        ? Math.round((data.branches.covered / data.branches.total) * 100)
        : 100
      const funcCov = data.functions.total > 0
        ? Math.round((data.functions.covered / data.functions.total) * 100)
        : 100

      lines.push(`${data.filePath}:`)
      lines.push(`  行: ${lineCov}% | 分支: ${branchCov}% | 函数: ${funcCov}%`)

      if (data.lines.missed.length > 0) {
        lines.push(`  未覆盖行: ${data.lines.missed.join(', ')}`)
      }
      if (data.functions.missed.length > 0) {
        lines.push(`  未覆盖函数: ${data.functions.missed.join(', ')}`)
      }
    }

    lines.push('================================')
    return lines.join('\n')
  }

  // 检查是否通过阈值
  isPassing(): boolean {
    const summary = this.getSummary()
    return (
      summary.lineCoverage >= this.thresholds.line &&
      summary.branchCoverage >= this.thresholds.branch &&
      summary.functionCoverage >= this.thresholds.function
    )
  }

  reset(): void {
    this.coverageData = []
  }
}
// CoverageAnalyzerTest.ets - 覆盖率分析工具的测试
import { describe, it, beforeEach } from '@ohos/hypium'
import { CoverageAnalyzer, CoverageData } from '../../../main/ets/test/CoverageAnalyzer'

export default function coverageAnalyzerTest() {
  describe('CoverageAnalyzer覆盖率分析', () => {
    let analyzer: CoverageAnalyzer

    beforeEach(() => {
      analyzer = new CoverageAnalyzer()
    })

    it('getSummary_单文件_覆盖率正确', 0, () => {
      analyzer.addFileCoverage({
        filePath: 'utils/Calculator.ets',
        lines: { total: 20, covered: 16, missed: [5, 6, 18, 19] },
        branches: { total: 6, covered: 4, missed: ['line5:if-else', 'line18:switch-case3'] },
        functions: { total: 5, covered: 4, missed: ['divide'] }
      })

      const summary = analyzer.getSummary()
      assertEqual(summary.lineCoverage, 80)
      assertEqual(summary.branchCoverage, 66.67)
      assertEqual(summary.functionCoverage, 80)
      assertEqual(summary.totalFiles, 1)
    })

    it('getSummary_多文件_加权平均', 0, () => {
      analyzer.addFileCoverage({
        filePath: 'utils/A.ets',
        lines: { total: 10, covered: 9, missed: [10] },
        branches: { total: 4, covered: 4, missed: [] },
        functions: { total: 3, covered: 3, missed: [] }
      })

      analyzer.addFileCoverage({
        filePath: 'utils/B.ets',
        lines: { total: 20, covered: 10, missed: [5, 6, 7, 8, 9, 10, 15, 16, 17, 18] },
        branches: { total: 6, covered: 2, missed: ['b1', 'b2', 'b3', 'b4'] },
        functions: { total: 5, covered: 2, missed: ['fn3', 'fn4', 'fn5'] }
      })

      const summary = analyzer.getSummary()
      // 行覆盖率: (9+10)/(10+20) = 63.33%
      assertClose(summary.lineCoverage, 63.33, 0.1)
      // 分支覆盖率: (4+2)/(4+6) = 60%
      assertEqual(summary.branchCoverage, 60)
    })

    it('isPassing_全部达标_返回true', 0, () => {
      analyzer.addFileCoverage({
        filePath: 'utils/Good.ets',
        lines: { total: 10, covered: 9, missed: [10] },
        branches: { total: 4, covered: 3, missed: ['b1'] },
        functions: { total: 5, covered: 5, missed: [] }
      })

      assertTrue(analyzer.isPassing())
    })

    it('isPassing_分支覆盖率不达标_返回false', 0, () => {
      analyzer.setThresholds({ branch: 80 })

      analyzer.addFileCoverage({
        filePath: 'utils/Bad.ets',
        lines: { total: 10, covered: 10, missed: [] },
        branches: { total: 10, covered: 5, missed: ['b1', 'b2', 'b3', 'b4', 'b5'] },
        functions: { total: 5, covered: 5, missed: [] }
      })

      assertFalse(analyzer.isPassing())
    })

    it('getUncoveredLines_返回未覆盖行号', 0, () => {
      analyzer.addFileCoverage({
        filePath: 'utils/Test.ets',
        lines: { total: 10, covered: 7, missed: [3, 5, 8] },
        branches: { total: 2, covered: 2, missed: [] },
        functions: { total: 3, covered: 3, missed: [] }
      })

      const missed = analyzer.getUncoveredLines('utils/Test.ets')
      assertEqual(missed.length, 3)
      assertEqual(missed[0], 3)
    })

    it('generateReport_生成文本报告', 0, () => {
      analyzer.addFileCoverage({
        filePath: 'utils/Calc.ets',
        lines: { total: 10, covered: 8, missed: [5, 6] },
        branches: { total: 4, covered: 3, missed: ['line5:else'] },
        functions: { total: 4, covered: 3, missed: ['divide'] }
      })

      const report = analyzer.generateReport()
      assertContain(report, '覆盖率报告')
      assertContain(report, 'Calc.ets')
      assertContain(report, 'divide')
    })
  })
}

四、踩坑与注意事项

坑点1:100%行覆盖 ≠ 100%分支覆盖

一段if-else代码,你只测了if分支,行覆盖率可能显示100%(因为else分支只有一行return,编译器可能把它算到if那行了)。但分支覆盖率只有50%。行覆盖率是最容易"虚高"的指标,分支覆盖率才是硬指标

坑点2:覆盖率数字的陷阱

80%的覆盖率,看起来不错。但如果那20%的未覆盖代码恰好是支付逻辑、权限校验、异常处理呢?看覆盖率不能只看总数,要看未覆盖的是哪些代码。关键路径的覆盖率必须接近100%,非关键路径可以适当放宽。

坑点3:为覆盖率而写测试

assertEqual(true, true)——这行断言覆盖了一行代码,但毫无意义。为了凑覆盖率数字写这种测试,是自欺欺人。覆盖率是测试质量的副产品,不是目标。先保证测试有意义,再关注覆盖率。

坑点4:忽略异常分支的覆盖

try-catch中的catch分支、throw new Error()的路径、边界条件的if分支——这些是最容易被忽略的,但恰恰是最容易出Bug的地方。异常分支的覆盖率应该重点检查,不能因为"正常路径都走了"就觉得够了。

坑点5:DevEco Studio覆盖率工具的使用

在DevEco Studio中运行测试时,需要选择"Run with Coverage"而不是普通的"Run",才能采集覆盖率数据。如果你跑了测试但没看到覆盖率报告,检查一下是不是选错了运行方式。

坑点6:覆盖率数据的合并

多次运行测试,每次可能覆盖不同的代码路径。覆盖率工具通常只显示最近一次运行的结果。要获取完整的覆盖率数据,需要一次性运行所有测试,或者使用工具合并多次运行的结果。

坑点7:生成代码和第三方代码的覆盖率

自动生成的代码(如protobuf、ORM模型)和第三方库的代码也会被计入覆盖率统计,但这些代码你通常不需要测。在覆盖率配置中排除这些目录,否则覆盖率数字会被"稀释"。


五、HarmonyOS 6适配说明

API差异表

功能/接口 HarmonyOS 5 HarmonyOS 6 变更说明
覆盖率采集 手动配置 @ohos/coverage 内置覆盖率模块
报告格式 控制台文本 HTML/JSON/LCOV 多格式报告
覆盖率类型 行覆盖 行+分支+函数 全面覆盖分析
增量覆盖率 Git diff集成 只看变更代码的覆盖
CI集成 手动脚本 原生CI支持 覆盖率门禁

行为变更

  1. @ohos/coverage模块:HarmonyOS 6内置了覆盖率采集模块,不再需要手动配置编译选项和运行参数。在build-profile.json5中开启coverage选项即可。

  2. 增量覆盖率:新增Git diff集成,可以只分析本次代码变更的覆盖率,不再需要看整个项目的覆盖率数字。这在代码审查时特别有用。

  3. 覆盖率门禁:CI流水线中可以设置覆盖率阈值,低于阈值的PR自动阻止合并。

适配代码

// build-profile.json5 - 开启覆盖率
{
  "app": {
    "coverage": {
      "enabled": true,
      "thresholds": {
        "line": 80,
        "branch": 75,
        "function": 90
      },
      "exclude": [
        "**/generated/**",
        "**/third_party/**"
      ],
      "reportFormats": ["html", "json", "lcov"]
    }
  }
}

// CI中运行覆盖率
// hdc shell aa test -b com.example.app -m entry_test --coverage
// 覆盖率报告输出到: entry/build/coverage/
// 覆盖率报告解读示例
// HTML报告中:
// - 绿色行:已覆盖
// - 红色行:未覆盖
// - 黄色行:部分覆盖(分支只走了一部分)
// - 行号旁边的数字:该行被执行的次数

// JSON报告结构:
// {
//   "summary": {
//     "lineCoverage": 85.5,
//     "branchCoverage": 72.3,
//     "functionCoverage": 90.0
//   },
//   "files": [
//     {
//       "path": "utils/Calculator.ets",
//       "lines": { "covered": [1,2,3,4,7,8], "missed": [5,6] },
//       "branches": { "covered": ["line3:if"], "missed": ["line3:else"] }
//     }
//   ]
// }

六、总结

维度 评价
学习难度 ⭐⭐
使用频率 ⭐⭐⭐⭐
重要程度 ⭐⭐⭐⭐

覆盖率的核心价值是发现盲区——那些从未被测试"碰"过的代码,就是Bug最可能藏身的地方。行覆盖率看"哪些行没走到",分支覆盖率看"哪些if/else没走到",函数覆盖率看"哪些函数没调过"。三个指标里,分支覆盖率最有价值,因为它能发现隐藏的逻辑路径。但别忘了,覆盖率只是手段,不是目的。100%覆盖率不等于零Bug,关键路径的覆盖比数字本身重要得多。覆盖率是测试的体检报告,不是成绩单

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。