代码覆盖率是什么?代码覆盖率一定要达到100%吗……
其实近几年,从软件质量联盟组织(CISQ)的报告就能看出软件质量带来的各类问题,2022年,美国软件公司因软件质量不佳至少损失了2.41万亿美元,还额外累积了约1.5万亿美元的技术债务。这恰好印证了,不良代码的部署、为了加快进度而忽略测试等行为,都在一点一点蚕食摇摇欲坠的软件质量。
细节决定成败。软件开发不仅需要精确度、前瞻性,更是一件需要持续关注细节的精细活儿。举个也许不太恰当的例子,如果我们在建造高楼时,忽视内部结构、复杂组件的检验的话,那后果将会不堪设想。
基于此,为了提高软件质量,对代码覆盖率的关注也要尽早提上日程了。
一、代码覆盖率是什么?
作为软件开发过程中的关键指标之一,代码覆盖率量化了测试过程中代码被执行的程度,通常以百分比的形式呈现:
代码覆盖率=(已测试执行的代码行数/软件总代码行数)*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%代码覆盖率也许很难达到,但在持续优化的过程中,我们能够更全面地测试,更加细致地思考。
“质量不是偶然的,它是持续改进的结果。”代码覆盖率可能只是其中一个手段,如何持续提升软件质量,才是团队需要明确并持续探索的目标。