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

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:语句覆盖

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

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

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

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

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

cpp 复制代码
 void demo(int aa, int bb) 
  {
        int a = aa; // 语句1
        int b = bb; // 语句2
        
        if (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语句为例,

cpp 复制代码
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%。

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

cpp 复制代码
if(a==1)  //表达式1
{
  //语句1;
}
语句2;
语句3;

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

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

7:条件覆盖

直接看代码

cpp 复制代码
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:遍历测试

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

cpp 复制代码
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:总结

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

相关推荐
kirito学长-Java1 小时前
springboot/ssm考试系统Java学生在线考试系统web学习论坛源码
java·spring boot·学习
澄澈i3 小时前
设计模式学习[9]---模板方法模式
c++·学习·设计模式·模板方法模式
池佳齐3 小时前
《AI大模型开发笔记》——Prompt提示词最佳实践
人工智能·笔记·prompt
Ice1663 小时前
算法学习笔记(十):位运算、数论等
笔记·学习
ZZZ_O^O4 小时前
【贪心算法入门第一题——860.柠檬水找零】
学习·算法·leetcode·贪心算法
我们的五年4 小时前
【Linux课程学习】:命令行参数,环境变量
linux·c语言·学习
梅子酱~4 小时前
Vue 学习随笔系列十七 -- 表格样式修改
javascript·vue.js·学习
尘佑不尘5 小时前
蓝队基础,了解企业安全管理架构
数据库·笔记·安全·web安全·蓝队
我真的太难了啊5 小时前
学习QT第二天
开发语言·qt·学习
特种加菲猫6 小时前
初阶数据结构之队列的实现
开发语言·数据结构·笔记