四种常见的代码覆盖率测试

news2025/2/3 4:54:31

您听说过“代码覆盖率”吗?在这篇文章中,我们将探讨什么是测试中的代码覆盖率,以及四种衡量它的常用方法。

什么是代码覆盖率

代码覆盖率是衡量测试代码测试了源代码百分比多少的指标。它可以帮助您识别可能缺乏适当测试的代码区域。

通常,覆盖率指标会这样去记录:

File% Statements% Branch% Functions% LinesUncovered lines
file.js90%100%90%80%89,256
coffee.js55.55%80%50%62.5%10-11, 18

当您添加新的功能和测试时,增加代码覆盖率百分比可以让您更加确信您的应用程序已经经过了彻底的测试。然而,还有更多的问题有待发现。

四种常见的代码覆盖类型

有四种常见的方法来收集和计算代码覆盖率:函数、行、分支和语句覆盖率。要查看每种类型的代码覆盖率如何计算其百分比,请思考以下计算咖啡成分的代码示例:

/* coffee.js */
 
export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;
 
  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }
 
  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }
 
  return {};
}
 
export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

不是很懂英语,去查了一下分别是:espresso-浓缩咖啡,americano-美式咖啡,mocha-摩卡咖啡

验证calcCoffeeIngredient函数的测试是

/* coffee.test.js */
 
import { describe, expect, assert, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-incomplete';
 
describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });
 
  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown');
    expect(result).to.deep.equal({});
  });
});

您可以在此demo中运行代码和测试,也可以签出存储库。

函数覆盖率

代码覆盖率:50%

/* coffee.js */
 
export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...
}
 
function isValidCoffee(name) {
  // ...
}

功能覆盖率是一个简单的指标。它表示计算出测试代码调用了源代码中百分之多少函数。

在代码示例中,有两个函数:calcCoffeeIngredient和isValidCoffee。测试代码只调用calcCoffeeIngredient函数,因此函数覆盖率为50%。

行覆盖率

代码覆盖率:62.5%

/* coffee.js */
 
export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;
 
  if (coffeeName === 'espresso') { // 1
    espresso = 30 * cup;  // 2
    return { espresso };  // 3
  }
 
  if (coffeeName === 'americano') {  // 4
    espresso = 30 * cup; water = 70 * cup; // 5
    return { espresso, water };  // 6
  }
 
  return {};  // 7
}
 
export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);  // 8
}

行覆盖率表示测试代码覆盖源代码的可执行代码行的百分比。如果一行代码仍然未执行,这意味着代码的某些部分没有经过测试。

代码示例有8行可执行代码,但是测试不执行americano条件(两行)和isValidCoffee函数(一行)。这使得线路覆盖率达到62.5%。

分支覆盖率

代码覆盖率:80%

/* coffee.js */
 
export function calcCoffeeIngredient(coffeeName, cup = 1) {
  // ...
 
  if (coffeeName === 'espresso') {
    // ...
    return { espresso };
  }
 
  if (coffeeName === 'americano') {
    // ...
    return { espresso, water };
  }
 
  return {};
}
…

分支覆盖率表示代码中执行分支或决策点的百分比,例如if语句或循环。它测定测试是否检查条件语句为true和false的分支。

代码示例中有五个分支:

只使用coffeeName调用calccoffeingredient √
用coffeeName和cup调用calcCoffeeIngredient √
coffeeName 是 浓缩咖啡 √
coffeeName 是 美式 ×
其他咖啡 √

测试涵盖除了咖啡是美式咖啡条件所有分支,所以分支覆盖率是80%。

语句覆盖率

代码覆盖率:55.55%

/* coffee.js */
 
export function calcCoffeeIngredient(coffeeName, cup = 1) {
  let espresso, water;
 
  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }
 
  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }
 
  return {};
}
 
export function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

语句覆盖率检测测试代码执行了代码中百分之几的语句。乍一看,您可能会想,这与线路覆盖不一样吗?实际上,语句覆盖类似于行覆盖,但考虑的是包含多个语句的单行代码。

在代码示例中,有8行可执行代码,但是有9条语句。你能找出包含两个语句的行吗?

答案揭晓:一行有两个语句的代码:espresso = 30 * cup; water = 70 * cup;

测试只覆盖了9条语句中的5条,因此语句覆盖率为55.55%。

如果您总是每行写一条语句,那么您的行覆盖率将与语句覆盖率相似。

您应该选择哪种类型的代码覆盖率?

大多数代码覆盖率测试工具包括这四种类型的通用代码覆盖率。选择哪个代码覆盖指标来确定优先级取决于具体的项目需求、开发实践和测试目标。

通常,语句覆盖率是一个很好的起点,因为它是一个简单且易于理解的指标。与语句覆盖率不同,分支覆盖率和函数覆盖率指标的是测试是调用条件(分支)还是函数。因此,它们是语句覆盖之后才应该考虑的。

一旦您获得了较高的语句覆盖率,您就可以继续进行提高分支覆盖率和函数覆盖率。

测试覆盖率与代码覆盖率相同吗

不一样。测试覆盖率和代码覆盖率经常被混淆,但它们是不同的:

测试覆盖率:一种度量测试套件覆盖软件特性的程度的定性度量。它有助于确定所涉及的风险水平。
代码覆盖率:一种量化的度量,用于度量在测试期间执行的代码的比例。它是关于测试覆盖了多少代码。
这里有一个简单的类比:把一个web应用程序想象成一个房子。

测试覆盖率衡量房子里覆盖了多少房间的程度。
代码覆盖率衡量了测试了多少房子。

100%的代码覆盖率并不意味着没有bug

虽然在测试中实现高代码覆盖率当然是可取的,但100%的代码覆盖率并不能保证代码中没有错误或缺陷。

实现100%代码覆盖率的毫无意义的方法

参考下面的测试:

/* coffee.test.js */
 
// ...
describe('Warning: Do not do this', () => {
  it('is meaningless', () => { 
    calcCoffeeIngredient('espresso', 2);
    calcCoffeeIngredient('americano');
    calcCoffeeIngredient('unknown');
    isValidCoffee('mocha');
    expect(true).toBe(true); // not meaningful assertion
  });
});

这个测试实现了100%的函数、行、分支和语句覆盖率,但是它没有意义,因为它实际上并没有测试代码。无论代码是否正常工作,expect(true).tobe(true)断言都会通过测试。

一个糟糕的度量标准比没有度量标准更糟糕

一个糟糕的指标会给你一种虚假的安全感,这比根本没有指标更糟糕。例如,如果您有一个达到100%代码覆盖率的测试代码,但是所有的测试都是无意义的,那么您可能会有一种错误的安全感,认为您的代码已经经过了很好的测试。如果不小心删除或破坏了应用程序代码的一部分,即使应用程序不再正常工作,测试仍然会通过。

要避免这种情况:

测试评估:编写和审查测试,以确保它们是有意义的,并在各种不同的场景中测试代码。
使用代码覆盖率作为指导方针,而不是作为测试有效性或代码质量的唯一度量。

在不同类型的测试中使用代码覆盖率

让我们仔细看看如何使用三种常见类型的测试的代码覆盖率:

单元测试。它们是收集代码覆盖率的最佳测试类型,因为它们被设计为覆盖多个小场景和测试路径。
集成测试。它们可以帮助收集集成测试的代码覆盖率,但是要谨慎使用。在这种情况下,您计算了源代码的大部分的覆盖率,并且很难确定哪些测试实际上覆盖了代码的哪些部分。尽管如此,计算集成测试的代码覆盖率对于没有良好隔离单元的遗留系统可能是有用的。
端到端(E2E)测试。由于这些测试的复杂性,测量E2E测试的代码覆盖率是困难和具有挑战性的。所以不应该使用代码覆盖,更好的方法是选择需求覆盖。这是因为E2E测试的重点是覆盖测试的需求,而不是关注源代码。

总结

代码覆盖率是衡量测试有效性的有用指标。通过确保代码中的关键逻辑经过良好测试,它可以帮助您提高应用程序的质量。

然而,请记住代码覆盖率只是一个度量标准。确保还要考虑其他因素,例如测试的质量和应用程序需求。

100%的代码覆盖率不是目标。相反,您应该使用代码覆盖以及一个包含各种测试方法的全面测试计划,包括单元测试、集成测试、端到端测试。

请参阅完整的代码示例,并使用良好的代码覆盖率进行测试。您还可以使用这个demo运行代码和测试。

/* coffee.js - a complete example */
 
export function calcCoffeeIngredient(coffeeName, cup = 1) {
  if (!isValidCoffee(coffeeName)) return {};
 
  let espresso, water;
 
  if (coffeeName === 'espresso') {
    espresso = 30 * cup;
    return { espresso };
  }
 
  if (coffeeName === 'americano') {
    espresso = 30 * cup; water = 70 * cup;
    return { espresso, water };
  }
 
  throw new Error (`${coffeeName} not found`);
}
 
function isValidCoffee(name) {
  return ['espresso', 'americano', 'mocha'].includes(name);
}

/* coffee.test.js - a complete test suite */
 
import { describe, expect, it } from 'vitest';
import { calcCoffeeIngredient } from '../src/coffee-complete';
 
describe('Coffee', () => {
  it('should have espresso', () => {
    const result = calcCoffeeIngredient('espresso', 2);
    expect(result).to.deep.equal({ espresso: 60 });
  });
 
  it('should have americano', () => {
    const result = calcCoffeeIngredient('americano');
    expect(result.espresso).to.equal(30);
    expect(result.water).to.equal(70);
  });
 
  it('should throw error', () => {
    const func = () => calcCoffeeIngredient('mocha');
    expect(func).toThrowError(new Error('mocha not found'));
  });
 
  it('should have nothing', () => {
    const result = calcCoffeeIngredient('unknown')
    expect(result).to.deep.equal({});
  });
});

最后感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:

这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!  

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1328861.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JVM】一、认识JVM

文章目录 1、虚拟机2、Java虚拟机3、JVM的整体结构4、Java代码的执行流程5、JVM的分类6、JVM的生命周期 1、虚拟机 虚拟机,Virtual Machine,一台虚拟的计算机,用来执行虚拟计算机指令。分为: 系统虚拟机:如VMware&am…

Oracle:JDBC链接Oracle的DEMO

1、引入jar包: 2、DEMO: package jdbc;import java.sql.*;public class OracleConnectionExample {public static void main(String[] args) throws SQLException {Connection conn null;PreparedStatement statement null;try {// Register JDBC dri…

FreeRTOS之二值信号量(实践)

信号量相当于一个标志,实现对资源多少的管理。 比如停车场空位的数量。 这里使用的是二值信号量,其队列长度为1,只有空或满两种状态。 1、步骤: 1.1、创建信号量 1.2、释放信号量 1.3、获取信号量 注:若想深入还…

c语言:文件操作(2),认识各种文件操作函数

fgets 作用 fgets是C语言标准库中用于从文件中读取字符串的函数。 fgets函数从指定的文件流stream中读取最多n-1个字符,或者直到遇到换行符(包括换行符在内),并将其存储到以str指向的字符数组中。读取的字符串会以null字符\0结…

Win系统修改Nginx配置结合内网穿透实现远程访问多个Web站点

文章目录 1. 下载windows版Nginx2. 配置Nginx3. 测试局域网访问4. cpolar内网穿透5. 测试公网访问6. 配置固定二级子域名7. 测试访问公网固定二级子域名 1. 下载windows版Nginx 进入官方网站(http://nginx.org/en/download.html)下载windows版的nginx 下载好后解压进入nginx目…

初级数据结构(七)——二叉树

文中代码源文件已上传&#xff1a;数据结构源码 <-上一篇 初级数据结构&#xff08;六&#xff09;——堆 | NULL 下一篇-> 1、写在前面 二叉树的基本概念在《初级数据结构&#xff08;五&#xff09;——树和二叉树的概念》中已经介绍得足够详细了。上一…

听GPT 讲Rust源代码--src/tools(21)

File: rust/src/tools/miri/src/shims/x86/mod.rs 在Rust的源代码中&#xff0c;rust/src/tools/miri/src/shims/x86/mod.rs文件的作用是为对x86平台的处理提供支持。它包含一些用于模拟硬件操作的shim函数和相关的类型定义。 具体来说&#xff0c;该文件中的函数是通过使用一组…

Python如何生成随机图形验证码

python生成随机图形验证码 使用python生成随机图片验证码,需要使用pillow模块 1.安装pillow模块 pip install pillow 2.pillow模块的基本使用 1.创建图片 from PIL import Image #定义使用Image类实例化一个长为400px,宽为400px,基于RGB的(255,255,255)颜色的图片 img1Ima…

Halcon 检测焊点短路

Halcon 检测焊点短路 read_image (Image1, D:/image/bilibili/photo/检测焊接短路 (4).bmp) dev_close_window () dev_open_window (0, 0, 512, 512, black, WindowHandle) dev_display (Image1) set_display_font (WindowHandle, 16, mono, true, false) threshold (Image1, …

modbus_tcp的实现 through python.

0.引言 当前科技似乎处于加速发展期&#xff0c;各个模块都在快速迭代&#xff0c;迭代的速度会让既有的一些经验产生问题&#xff0c;在用python实现modbus_tcp协议时&#xff0c;网上流传的一些代码中import语句会出现问题。导致pymodbus模块用起来很不好用。 这个原因出在…

集合工具类Collections

概述 java.utils.Collections:是集合工具类 作用&#xff1a;Collections并不属于集合&#xff0c;是用来操作集合的工具类。 Collections常用的API Collections排序相关API 使用范围&#xff1a;只能对于List集合的排序。 排序方式1&#xff1a; 注意&#xff1a;本方式…

23 在HST场景中,考虑物理层相关技术

文章目录 实验参数A&#xff1a;解调参考信号(DMRS)1 DMRS分配图2 实验结果图figur3figur4 3. 实验结论 B 映射资源元素。1 映射图2 实验结果图figure 6figure 7 3. 实验结论figure8 3 补充结论 C μ1 实验结果图&#xff1a;figure 9figure 10 2 结论 实验参数 注释&#xff1…

数据结构和算法-红黑树(定义 性质 查找 插入 删除)

文章目录 红黑树的定义和性质为什么要发明红黑树&#xff1f;红黑树怎么考总览红黑树的定义实例&#xff1a;一颗红黑树练习&#xff1a;是否符合红黑树的要求一种可能的出题思路补充概念&#xff1a;节点黑高 红黑树的性质 红黑树的查找红黑树的插入实例小结与黑高相关的理论 …

web前端游戏项目-堆木头游戏【附源码】

web前端游戏项目-堆木头游戏 《堆木头》游戏玩法简单&#xff0c;通过鼠标点击放木头的按钮&#xff0c;叠加在一起&#xff0c;构建出各种结构。游戏适合所有年龄段的孩子&#xff0c;可以锻炼孩子的动手能力和手眼协调能力&#xff0c;激发孩子的创造力和想象力 运行效果 …

使用GitZip下载GitHub指定文件

目录 一、GitZip二、安装GitZip三、链接GitHub四、检验是否安装成功五、总结 一、GitZip GitZip是一个非常实用的浏览器插件&#xff0c;它主要有以下几个优点&#xff1a; 下载指定文件&#xff1a;在我们浏览Github时&#xff0c;如果只想下载某个子目录的内容&#xff0c;…

15-高并发-如何扩容

对于一个发展初期的系统来说&#xff0c;不太确定商业模型到底行不行&#xff0c;最好的办法是按照最小可行产品方法进行产品验证&#xff0c;因此&#xff0c;刚开始的功能会比较少&#xff0c;是一个大的单体应用&#xff0c;一般按照三层架构进行设计开发&#xff0c;使用单…

服务器IBM x3650 m2 管理口访问故障处理

服务器的内存告警后&#xff0c;连接管理口查看信息&#xff0c;管理口状态灯显示正常&#xff0c;但是无法ping通和访问。 处理过程如下&#xff1a; 1、在centos 6.6中安装ipmitool&#xff0c;替换为阿里云的yum源&#xff0c;然后安装。 # wget -O /etc/yum.repos.d/Cen…

【String、StringBuilder 和 StringBuffer 的 区别】

✅ String、StringBuilder 和 StringBuffer 的 区别 ✅典型解析✅扩展知识仓✅String 的不可变性✅ 为什么JDK 9 中把String 的char[ ] 改成了 byte[ ] ? ✅为什么String设计成不可变的✅缓存✅安全性✅线程安全✅hashcode缓存✅ 性能 ✅String 的 " " 是如何实现的…

Ubuntu 常用命令之 du 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 Ubuntu系统下的du命令是一个用来估计和显示文件和目录所占用的磁盘空间的命令。du是“disk usage”的缩写&#xff0c;这个命令可以帮助用户了解磁盘被哪些文件和目录使用。 du命令的常见参数有 -a&#xff1a;列出所有文件和目…

STM32的以太网外设+PHY(LAN8720)使用详解(3):PHY寄存器详解

0 工具准备 1.野火 stm32f407霸天虎开发板 2.LAN8720数据手册 3.STM32F4xx中文参考手册1 PHY寄存器 前面介绍到&#xff0c;站管理接口&#xff08;SMI&#xff09;允许应用程序通过2线时钟和数据线访问任意PHY寄存器&#xff0c;同时该接口支持访问最多32个PHY&#xff0c;也…