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的值该如何取?为了方便理解,我自创了个概念,先计算单个表达式的分支覆盖率
- a==1,表达式1=ture 表达式1的分支执行率=50%;
- 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真, 子条件2真:对应赋值 a=1,b=1
- 子条件1真, 子条件2假:对应赋值 a=1,b=0
- 子条件1假, 子条件2假:对应赋值 a=0,b=0
- 子条件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:总结
单元测试中的覆盖率测试,如何选择合适的测试率,作为指标。要以具体的代码结构作为参考