代码覆盖率:质量的黄金标准,你的软件有多可靠?

代码覆盖率是什么?代码覆盖率一定要达到 100% 吗……

最近很多客户在咨询禅道的时候,提出来了上述这类问题。我们能明显感受到大家对软件质量的重视程度越来越高。在重视代码覆盖率的同时,大家也能更为及时地暴露出现有软件开发流程中存在的各类风险。

今天,我们就来好好聊聊代码覆盖率,用一篇文章讲透代码覆盖率的各类问题。

其实近几年,从软件质量联盟组织(CISQ)的报告就能看出软件质量带来的各类问题,2022 年,美国软件公司因软件质量不佳至少损失了 2.41 万亿美元,还额外累积了约 1.5 万亿美元的技术债务。这恰好印证了,不良代码的部署、为了加快进度而忽略测试等行为,都在一点一点蚕食摇摇欲坠的软件质量

细节决定成败。软件开发不仅需要精确度、前瞻性,更是一件需要持续关注细节的精细活儿。举个也许不太恰当的例子,如果我们在建造高楼时,忽视内部结构、复杂组件的检验的话,那后果将会不堪设想。

codecoverage2.jpg
codecoverage1.jpg

基于此,为了提高软件质量,对代码覆盖率的关注也要尽早提上日程了。

一、代码覆盖率是什么?

作为软件开发过程中的关键指标之一,代码覆盖率量化了测试过程中代码被执行的程度,通常以百分比的形式呈现:

代码覆盖率=(已测试执行的代码行数/软件总代码行数)*100%

简而言之,代码覆盖率能够展示测试对代码的覆盖广度

代码覆盖率能帮团队识别未被测试的代码区域,从而确认这些区域是否隐藏着未被发现的错误或潜在问题。

需要注意的是,100% 的代码覆盖率并不意味着软件毫无缺陷。团队真正的重点应放在编写有意义的测试上,放在编写能够覆盖各种场景(比如极端情况、潜在错误路径)的测试中。

二、如何计算代码覆盖率?

我们一般会通过工具,将代码行覆盖率的数据集中存储在中心系统内。可以这样说,也类似于在代码的关键部分安装传感器,以便在测试执行过程中监控哪些代码已被执行。

通常这类工具会通过以下几种标准进行计算:

1.函数覆盖率

这一指标衡量的是在测试过程中执行的函数或子例程的百分比。它显示了测试期间至少被调用一次的函数数量。

达成 100% 的覆盖率意味着确保每个定义的函数至少被调用一次,从而验证功能的遍历性。

计算公式:函数覆盖率=****(已经执行的函数数/总函数数)*100%

例子:

# File: calculator.py
def add(a, b):
   return a + b
def subtract(a, b):
   return a - b

在这个例子中,实现 100% 的函数覆盖率意味着在测试用例中执行“add”和“subtract”这两个函数。

2.语句覆盖率

语句覆盖率关注的是函数内单个语句的执行。完整的语句覆盖率主要用于识别死代码(永远不会执行的代码)、确保代码的每个部分都可访问和测试。这一指标也有助于识别缺失的语句以及未使用过的语句和分支。

计算公式:**语句覆盖率=****(**已经执行的语句数/总语句数)*100%

例子:

# File: calculator.py
def multiply(a, b):
   result = a * b
   print(result)
   return result

语句覆盖率涉及执行计算(“result = a * b”)和“print(result)”语句。

3.分支覆盖率

在编码中,分支指的是代码中的点,它可以将程序流程导向一个或多个路径。这种类型的覆盖通过关注代码中的决策点来扩展语句覆盖的概念。分支覆盖率衡量的是测试过程中已被采用的分支的百分比。完整的分支覆盖率能够确保所有可能的决策结果都被考虑和测试到。

计算公式:分支覆盖率=****(已经执行的分支数/总分支数)*100%

例子:

# File: number_classifier.py
def is_positive(num):
      if num > 0:
              return True
      else:
         return False

为了达到 100% 的分支覆盖率,需要编写测试用例覆盖“if”和“else”分支。

4.条件覆盖率

条件覆盖率主要是对函数中布尔条件的评估。布尔条件指编程中计算结果为“True”或“False”的表达式或语句。条件覆盖率确保每个条件都经过真假测试,能够确保决策制定过程的正确性。

计算公式:条件覆盖率=****(已经执行的条件数/总条件数)*100%

例子:

# File: voting_age_checker.py
def cab_vote(age):
   return age >= 18

实现条件覆盖涉及测试“年龄”大于和小于 18 的输入。

多重条件决策覆盖率(MC/DC)是一种更严格的条件覆盖形式,它能确保每个条件独立地影响决策结果。

例子:

# File: loan_approval.py
def approve_loan(income, credit_score):
   return income > 50000 and credit_score > 700

MC/DC 覆盖率要求测试用例能够独立改变“income”或“credit_score”中的任何一个,从而影响决策结果。

除这四种最常见的覆盖率外,还会有行覆盖率、参数值覆盖率等。行覆盖率衡量的是测试期间执行的代码行数,但可能无法识别行的部分执行过程。参数值覆盖率确保使用各种输入值测试函数,主要用于测试参数处理、边界条件以及不同输入场景下函数的整体稳健性等问题。

在测试用例中,通过不同覆盖率的组合,能够更为全面地保证代码质量。

举一个较为复杂的例子(根据各种条件确定某人是否有资格享受折扣):

# File: discount_calculator.py
def calculate_discount(age, income, has_membership):
   """
   Calculates the discount eligibility based on age, income, and membership.
   """
   discount = 0
   if age >= 18:
       if income > 50000:
           discount = 10
           if has_membership:
               discount += 5
       elif income > 30000:
           discount = 5
   else:
       discount = 0
   return discount

测试如下:

# File: test_discount_calculator.py
from discount_calculator import calculate_discount
def test_eligible_for_discount():
   result = calculate_discount(25, 60000, True)
   assert result == 15
def test_eligible_for_partial_discount():
   result = calculate_discount(30, 40000, True)
   assert result == 5
def test_not_eligible_for_discount():
   result = calculate_discount(16, 35000, False)
   assert result == 0  

在这个例子中,我们有三个测试用例覆盖不同的场景。但我们会发现,要想所有可能的代码路径的覆盖率达到 100% 会很难,这就意味着该函数本身可能会过于复杂,需要重新评估。

三、代码覆盖率与测试覆盖率

在实际应用中,很多人会将“代码覆盖率”和“测试覆盖率”这两个术语混淆。

实际上,代码覆盖率衡量的是代码的执行程度,它主要明确已经执行了哪些代码,哪些代码还未经测试;而测试覆盖率主要体现测试已经覆盖了哪些功能特性。

测试覆盖率可以通过各种测试方案实现:

  • 单元测试来验证最小可测试单元(如函数、方法)的准确性;
  • 响应式测试用于验证 Web 应用或网站在不同设备和屏幕尺寸上的显示和运行情况;
  • 跨浏览器测试确保 Web 应用或网站在不同浏览器上的兼容性和一致性;
  • 集成测试验证系统各组件或模块间的交互;
  • 验收测试评估软件应用是否满足既定要求并做好部署的准备;
  • 回归测试确保新的代码更改不会对现有功能造成负面影响;
  • ……

可以这样说,这两者都是提升软件质量应重点关注的维度。

在探讨代码覆盖率的过程中,我们不是仅在审视一段段冷冰冰的代码,而是在探索一个更为深远的话题——如何确保构建的软件稳固、可靠。即使在虚拟的世界里,也存在着无数的可能性与变数。理想的 100% 代码覆盖率也许很难达到,但在持续优化的过程中,我们能够更全面地测试,更加细致地思考。

“质量不是偶然的,它是持续改进的结果。”代码覆盖率可能只是其中一个手段,如何持续提升软件质量,才是团队需要明确并持续探索的目标。

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...