C代码—单元测试中的覆盖率—学习笔记

embedded/2024/11/18 11:17:05/

1:覆盖率的概念

类比到生活中,我们常听到,以下描述,

**1)某个地区,家庭网络宽带覆盖率

**2)私家车覆盖率(普及率)

要了解的是,覆盖率是如何统计,以家庭网络宽带覆盖率为例,即某个地区内:装有宽带的家庭/家庭总数=家庭网络宽带覆盖率。

分子是,满足一定条件的统计数量,分母是统计的样本数量,我们说的XXX_覆盖率,前面的XXX就是修饰和限定性质的修饰语。

2:为什么单元测试要测试覆盖率?

我个人总结,覆盖率测试有以下意义

1:避免死语句,看下面一段代码 

void fun_1(unsigned char var_1)

{

 if(var_1<0)

   printf("1:条打印语句被执行");

else

 printf("2:条打印语句被执行");

}

从上面一段代码可知, var_1不可能<0,这种情况不可能发生的。printf("1:条打印语句被执行");这条语句是不可能被执行。

如果实际项目中,大量出现这种死语句,那也是不可以接受的。占用了大量的flash内存,却不可能执行到的语句

2:避免死循环

int main()

{

  while(1)

  {

   printf("3:条打印语句被执行");

  }

 printf("4:条打印语句被执行");

}

这个函数 执行后,会一直在while中死循环, printf("4:条打印语句被执行");语句不会被执行。此时当死循环执行到一定次数,也可能导致系统崩溃。

所以我们在学习递归函数(函数调用自身时),学习资料中明确规定,递归函数要有一个明确,且一定能实现的结束条件。

3:测试程序稳固性,这个描述可能不太专业,很多资料叫做鲁棒性

void fun_2(char val_2,char val_3)

{

  if(val_3!=0)

  {

    if(val_2/val_3==1)

     {

       printf("5:条打印语句被执行");

     }

    else

    {

        printf("6:条打印语句被执行");

    }

  }

else

  {

   pinrtf("val_3错误输入,请重新输入")

  //复杂项目中,可以在这里加上一些措施,比如重新给val_3赋值一个不为0的值,或直接调用模块,将系统初始化,重新运行,防止系统跑飞

  }

}

 如果形参val_3被传入值=0,则可能导致程序崩溃,但是代码,作了一定的处理,我们测试这种故障状态时,就需要将代码执行到这一步。查看整个程序是否还能正常运行。

4 覆盖率的分类

1语句覆盖StatementCoverage(SC)语句定义,只有以;结束的表达式,才能称为语句
2分支覆盖Branch Coverage(BC)
3条件覆盖DecisionCoverage(DC)
4分支条件覆盖
5路径覆盖

5:语句覆盖

计算公式:程序执行到的语句总数 / 全部语句的总数

执行意义:确保函数不存在死语句

              :确认函数每条语句的执行,都不会导致程序出现跑飞,卡死现象

测试方法:具体测试方法要根据代码的具体情况来处理,能够执行单元测试的工具,从测试原理上来说都是一致的,即通过控制条件,让每一条语句都执行。

单元测试的工具,控制条件,都是通过指定输入值,根据代码结构,判断语句是否被覆盖。

 void demo(int aa, int bb) {int a = aa; // 语句1int b = bb; // 语句2if (a == 0 && b == 0) {printf("a = 0 and b = 0"); // 语句3} else if (a == 1 || b == 1) {printlf("a = 1 or b = 1"); // 语句4} else{println("a = " + a+ ", b = " + b); // 语句5}}

语句1和语句2,属于顺序结构,任何情况下都会执行。不需要考虑传入实参的值。

**1)a==0&&b==0,语句3执行

**2)a==1,b任何值,执行语句4

**3)a==2,b任何值,执行语句5

对应到测试用例中,只要利用测试工具,设置3个Testcase,就可以满足语句100%覆盖

  • 语句覆盖,是各种覆盖中最常见的,我个人的理解:语句覆盖是覆盖测试中的基本测试项(同时也是很重要的)
  • 语句覆盖,适合任何代码结构,这里举一个例子,如果一个测试单元,全部是顺序结构,没有任何判断,循环,分枝。这种情况下,接下来说的分支覆盖,条件覆盖,路径覆盖(其实只有一条路径)就没有测试的必要了。

6:分支覆盖

  • 计算公式:程序执行到的分支总数 / 全部分支总数

以最简单的if语句为例,

int Val_a;
if(a==1) //a==1:表达式0
{ 
//执行语句;
}else if(a==2) //a==2:表达式1
{//执行语句1;
}
......(很多的else if)
else if(a==m)  //a=m:表达式m
{// 执行语句m
}else
{//执行语句m+1;
}  

if(表达式1),中的:

  • 表达式1=1(为true),执行语句1,
  • 表达式1=0(为flase),执行下面第一个 else if
  • 继续执行判断,表达式m=1(为true),执行语句m
  • 表达式m=0(为flase)
  • 当所有 if后的表达式,都不满足true时,最后执行else中的的执行语句m+1

上面的代码中,每个if对应两个分支,则总分支数量=2m。想要达到分支覆盖100%,我们需要怎么实现,a的值该如何取?为了方便理解,我自创了个概念,先计算单个表达式的分支覆盖率

  1. a==1,表达式1=ture  表达式1的分支执行率=50%;
  2. a>1,表达式1=false 表达式1的分支执行率=50%+50%=100%;,此时我们a取值多少合适?我们先取a=3。此步骤直接跳过了 表达式2的判断。导致先判断了表达式3。此时表达式2的分支执行率=0%。表达式3的分支执行率=50%。这样显然不合理,你后面还会想起来执行一次表达式2吗?

故,最简单有效且不易出错的方式是

a==1,a==2,a==3 。。。a==m执行一遍,就可以完成分支覆盖率100

小结与思考:

上面的例子中,我们执行覆盖率统计时,达到分支覆盖100%的同时,语句覆盖也是100%。

分支覆盖,和语句覆盖的关系

if(a==1)  //表达式1
{//语句1;
}
语句2;
语句3;

这种情况下,语句覆盖只需要 a==1即可,分支覆盖还要添加一个a!=1的情况。故总结如下:

分支覆盖,肯定包含语句覆盖,语句覆盖不一定能包含分支覆盖。也可以理解为,语句覆盖是分支覆盖的子集。

7:条件覆盖 

直接看代码

void demo(int aa, int bb) {int a = aa;int b = bb;if (a != 0 && b !=0){a*b;}else{a/b;}}

if 中的表达式=(a != 0 && b !=0),本质上可以再划分为两个子表达式,a != 0、b !=0。

如果只考虑分支 a和b的取值 只要两个组合就可以覆盖分支覆盖,如下:

  • a=1,b=1
  • a=0,b任意值均可

但是,b任意值下,b=0,的情况下。这种情况是错误的,因为执行语句 a/b时,b是不能等于0的。

但是,如果要满足条件覆盖,

计算公式: 条件覆盖率分子/分母;

分母如何计算,以上面代码为例,a != 0 && b !=0,分为两个子条件,(a != 0 为子条件1,b !=0为子条件2)描述如下

  1. 子条件1真, 子条件2真:对应赋值 a=1,b=1
  2. 子条件1真, 子条件2假:对应赋值 a=1,b=0
  3. 子条件1假, 子条件2假:对应赋值 a=0,b=0
  4. 子条件1假, 子条件2真:对应赋值 a=0,b=1

通过以上分析,可知,条件覆盖分母数量=4,此时要想达到100%。覆盖率,取以上4种取值即可。

小结

  • 从这一段的描述中,可以看出,条件覆盖>分支覆盖的。如果有条件尽量做条件覆盖测试

条件覆盖的意义

8:遍历测试

遍历测试,在判断条件中,有些是存在一个区间的

void demo_1(int aa, int bb) {int a = aa;int b = bb;if (a >= 0 && a<=10){a*b;}else{a/b;}}

    注意看这条语句, if (a >= 0 &&a<=10),遍历测试中我们就需要设置a=0-10都测试一遍。

这时又有人问?你这区间值小,可以遍历。如果区间很大 if (a >= 0 &&a<=10000),也可以遍历吗?

这种情况下,可以采取 ,以下逻辑测试 初始值a=0,然后每一步+100,直到a=10000。100个测试用例通过脚本控制,也是很容易实现的。

或者等分法,10000/10=1000,a取值0,和500,1500 2500 。。。9500 10000

9;路径覆盖

路径覆盖的定义,路径我个人理解的定义如下

单元测试的路径:即一个函数从开始执行,到结束函数运行的执行路径

 以上代码,只有两个路径。如果要做路径覆盖,其实也不复杂。

10:总结

单元测试中的覆盖率测试,如何选择合适的测试率,作为指标。要以具体的代码结构作为参考


http://www.ppmy.cn/embedded/138519.html

相关文章

机器人操作臂逆运动学

机器人操作臂的逆运动学&#xff08;Inverse Kinematics&#xff0c;简称IK&#xff09;是机器人学中的一个核心问题&#xff0c;涉及确定机器人关节参数以实现末端执行器&#xff08;如手爪、工具等&#xff09;达到指定位置和姿态。逆运动学在机器人控制、路径规划、人机交互…

[JAVA]MyBatis框架—如何获取SqlSession对象实现数据交互(基础篇)

假设我们要查询数据库的用户信息&#xff0c;在MyBatis框架中&#xff0c;首先需要通过SqlSessionFactory创建SqlSession&#xff0c;然后才能使用SqlSession获取对应的Mapper接口&#xff0c;进而执行查询操作 在前一章我们学习了如何创建MyBatis的配置文件mybatis.config.xm…

Playwright 快速入门:Playwright 是一个用于浏览器自动化测试的 Node.js 库

Playwright 是一个用于浏览器自动化测试的 Node.js 库&#xff0c;它支持 Chromium, Firefox 和 WebKit 浏览器引擎。Playwright 提供了一套强大的 API 来进行网页自动化测试&#xff0c;包括页面导航、元素选择、表单提交等操作&#xff0c;并且能够处理现代网页中的异步加载内…

【知识科普】微内核架构与宏内核架构

微内核与宏内核 微内核一、微内核的定义二、微内核的特点三、微内核的优缺点四、微内核的应用场景操作系统 宏内核一、宏内核的定义二、宏内核的特点三、宏内核的优缺点四、宏内核的应用场景 微内核架构与宏内核架构简单比较微内核的优势宏内核的优势面向未来的架构 微内核 微…

重构代码之内联临时变量

内联临时变量 是一种重构技术&#xff0c;用于简化代码结构、提高可读性和可维护性。它的主要思路是将只被赋值一次的临时变量直接替换为表达式本身&#xff0c;从而减少不必要的变量定义。 一、使用场景 当一个临时变量仅用于保存某个表达式的结果&#xff0c;且没有其他用途…

flutter插件:录制系统播放的声音

该插件基于flutter包 flutter_screen_recording 和 github库 SystemAudioCaptureAndroid&#xff0c;实现了在安卓手机上录制系统播放声音的功能&#xff0c;也就是说&#xff0c;只要一个安卓应用没有设置不允许其它应用录制声音&#xff0c;该插件可以录制该应用播放的声音。…

mongodb基础知识

认识mongodb MongoDB 是一个介于关系数据库和非关系数据库之间的产品&#xff0c;是非关系数据库当中功能最丰富&#xff0c;最像关系数据库的。MongoDB中将数据存储为一个文档&#xff0c;文档由键值对(key>value)组成&#xff0c;MongoDB 文档类似于 JSON 对象。字段值可…

华为机试HJ48 从单向链表中删除指定值的节点

首先看一下题 描述 输入一个单向链表和一个节点的值&#xff0c;从单向链表中删除等于该值的节点&#xff0c;删除后如果链表中无节点则返回空指针。 链表的值不能重复。 构造过程&#xff0c;例如输入一行数据为: 6 2 1 2 3 2 5 1 4 5 7 2 2 则第一个参数6表示输入总共6个节点…