动态测试_语句覆盖&分支覆盖实验总结

学习软件测试时,进行了为期一个月的实验。选择要测试的项目选了很久【事实证明完美主义的结果是拖延】。在最后一周时间内,泛读了两三天+最后三天不间断的每天近12h高强度实验下,完整了覆盖率测试及报告分析。卫冕了ddl战士的称号。

在此将过程中学到的知识(主要是试错中)做个总结,确实很有收获。

1.关于头文件_头文件的重新封装

可以看到,大型功能函数的定义都近百行;因而整个.C文件近2K行,难以短时间查阅理清逻辑;

于是初步想法:函数封装到头文件里去;

常见的模式:"header.h"里,有函数a的声明; 定义在main.c(即上面的主文件)里实现;

我仍觉得量大,于是把定义与声明都放在了"header.h"里,自以为很方便;

但是会报错重定义....后来才知道不能把定义与声明都放在头文件里,这样会导致反复编译链接

(Q:如何实现函数的定义抽离出主函数??)

2.实现自动化交互--模拟终端输入:

可以用gmock。

我这里采用的是Expect-send 可以通过代码模拟终端输入;

set timeout 1.5 设置expect每次最多等待1.5s;

spawn 启动可执行文件进程;

expect  "xx" --如果遇到终端输出“xx”,那么send"123456",即在命令行终端输入123456;

最后expect  eof 表示结束;

注意:如果expect 没有等到对应的输出内容,也会执行send.所以使用expect只是严格的规定了顺序和输入的节奏;

【Q:是否有分支类的写法,使得没有出现就不send?下一步学习查阅这方面】。

3.windows .vs. linux 部分字符/编码差异:

编码格式/换行格式不同 :windows下和linux下编辑的文件带有不同的换行符.通过Cat  无法查看;

不可打印字符,故输出无法查看】

使用 cat -A a.txt 查看:

而linux下创建的文件输出同样内容,cat-A 输出查看如下:

解释:

$表示行末;^M是windows下的回车换行;

^I是linux下的制表符,即Tab;而windows下的Tab是转为四个空格;

4.实验细节_报错及解决

 4.1函数问题:多个main/函数重定义

(1)关于存在多main函数入口(入口冲突)

(2)函数重定义_ [主要是头文件里定义与声明放在一起]

解决(1):指定入口函数

gtest测试文件的主函数main+项目文件的主函数main.

保留项目main,测试文件不使用main,具体实现如下:

$ gcc -testfiles -e test_3 test.c -o test.out

使用 -e 指定入口函数即可;

【参考没有了 main 函数,程序还能跑吗?_没有main函数的c++程序也可以执行-CSDN博客

解决(2):确保每个函数在整个源文件中只定义一次

“multiple definition”(多重定义)表示列出的函数(例如 putch、getch、get1s、main 等)在编译的文件中被定义了多次。

因为有多个源文件包含全局函数定义,并且这些函数在多个文件中被定义。

方案1:单列头文件

将函数定义移动到一个单独的头文件中,并使用 inline 关键字标记那些短小且对性能关键的函数;

方案2:使用唯一性的声明:

可用1)# pragma once 表明:

一种预处理器指令,用于确保头文件只被编译一次;

当编译器遇到 #pragma once时,它会检查当前的头文件是否已经被包含过;

如果是,则忽略后续的包含操作,如果否,则继续处理该头文件。

    2) #ifndef-#define-#endif【头文件保护宏】

如果该宏已经被定义过,则条件为假,预处理器会跳过后续的代码块;

如果该宏未被定义,则条件为真,预处理器会继续处理后续的代码块。

方案3:

采用方案1,2后,仍然重定义

于是将头文件部分冲突函数的实现放入对应函数文件内,与使用inline 异曲同工;

4.2不同系统(linux vs windows)字符格式问题

(1)makefile报错:没有分隔符;

(2).sh报错:解释器错误,没有那个文件或目录_ [脚本文件在windows下编辑过]

解决(1):cat -A file 查看

使用cat -A file 查看:【呈现非打印字符】【Linux/Unix 命令,^:控制字符;$:行尾】

因为是在windows和linux的sharefiles下共同传输编辑文件,所有会有编码换行格式的不兼容;

Makefile如下:

clean:
	rm -rf *.gcno
	rm -rf *.gcda
	rm -rf *.gcov
html:
	g++ -o test421_1 minitest.cpp -fprofile-arcs -ftest-coverage -lgtest -lgtest_main -pthread
	./simulate_input.sh 
	gcov test421_1-minitest.gcda 
	gcovr --html-details -o ./test421/t1.html

2html:
	g++ -o test421_2 test.cpp -fprofile-arcs -ftest-coverage -lgtest -lgtest_main -pthread
	#./simulate_input.sh 
	./2simulate.sh
	#./test421_2
	gcov test421_2-test.gcda 
	gcovr --html-details -o ./test421/t2.html
#g++ -o test421_1 minitest.cpp -lgtest -lgtest_main -pthread
解决(2):删除这个字符
windows下每一行的结尾是\n\r;  v.s. linux下文件的结尾是\n;
windows下编辑过的文件在linux下打开时,每一行的结尾就会多出来一个字符\r;
用cat -A yourfile时可以看到\r字符被显示为^M;

输入如下命令,删除这个字符;

sed -i 's/\r$//' create_infomember.sh

参考:

Shell脚本bash: /bin/bash^M:解释器错误: 没有那个文件或目录 -- 报错_/bin/bash 解释器错误-CSDN博客

4.3覆盖操作不当,导致覆盖信息不能生成/有误

(1)时间戳timestamp不同:

(2)关于test的函数实际覆盖与显示的覆盖行情况不一_[显示注释被执行2次]

(3)生成.gcno而不生成.gcda,且覆盖率0%

解决(1)(2):删除前一次的文件(.gcda/.gcno)

因为覆盖信息里有时间信息,在未删除前一次的覆盖信息时,无法生成新的覆盖信息;

后续发现,是前一次没有make clean 清理掉.gcov文件,导致文件冲突,会报错timestamp时间戳问题,也可能不报错,结果就是显示混乱【E.g: 显示注释处未覆盖等等】

即每次make html 前 make clean;

解决(3):正确运行完程序,不要非法终止;

-fprofile-arcs 生成.gcno;【编译时生成】

-ftest-coverage 生成.gcda;【注意:覆盖率信息是在运行程序时生成的】

故确保程序正确执行【中途交互出错则会导致失败,覆盖率为0%】(本次测试中的大部分时间花在调试此bug上) 

4.4 Bash脚本报错

(0)/bin/bash^M:解释器错误[同4.2(2)]

(1)expect-send 交互失败

(2)send用法错误

解决(1):

进程已结束,expect不到对应的语句,自然无法send:可以看到是 “not open ”,即对应进程未启动;发现是交互顺序有误,导致进程提前结束,故send失败;

解决(2):

Cat -A 查看,发现把注释也同样识别为输入了,删除或换行注释即可;

4.5文件权限导致的_段错误+创建文件失败

(1)(2)问题如题目.

 即访问非法内存,这里是给fopen()只读的权限,而后续有写入操作,导致访问了"不可访问的内存",报段错误.

解决(1):chmod +x minitest.cpp 【符号模式】

关于chmod:字符模式(即+x等对应权限的字母,如上)/数字模式(示下):

读取权限:4;写入权限:2;执行权限:1

读取和执行:数字模式为 5(4 + 1)。

读取、写入和执行权限:7(4 + 2 + 1)。

尾附部分实验报告。

一、实验要求

 设计测试用例配合gtest测试,并使用GCOV分析一个1500行以上的真实C/C++开源程序(不能使用程序自带的测试用例!!!) 提交实验报告,内容包括:

  • 1)分析过程:使用过程、编译运行命令、测试用例构建等;
  • 2)分析结果:覆盖率信息

二、实验内容:

2.1实验环境:

内核及ubuntu版本如下:

2.2测试对象:

2.2.1程序的规模:

总共计数为:1650行;

Cloc . 】查看对应代码数量如下: 实际C代码行数为1297

2.2.2代码结构:

main函数模拟整个流程,其余为实现函数。具体见下“主要函数”。

其主函数流程如下【一个while循环下不断判断执行】:

主要函数如下:

2.3测试设计:

2.3.1编写测试用例的考虑:

分析代码,可以看到get1ch【一个输入函数】居多:

且输入输出同样居多,于是采用Bash的expect-send模拟输入输出的用户交互,

且void类函数居多,传参少,大体是用户交互及自带的txt文件【存储系统内的各类图书、用户信息】

TEST函数简便,如下:

Bash命令行交互内容复杂【共编写了约400行进行输入输出模拟】,部分如下:

最后确定了使用bash语言中的expect-send模式进行输入模拟【尝终于实现自动化测试】;

2.3.4实验流程

2.3.4.0 函数封装及文件调整(首个项目尝试过程)

注:这是第一个项目的开始流程,由于过程中的操作让我收获很多,于是同样放在此处:

第一个项目中起初得到的整个main.c文件为1853行(见图1)于是调整文件,把函数声明和定义等封装为头文件(并未更改任何代码),以便进行单元测试;(结构调整图and各种头文件内部代码如下)

图表 1 defineFunctions.h(函数定义)

2.3.4.2实现代码模拟终端输入

但是发现每次需要在终端输入,于是研究2天研究如何模拟终端输入:

最后成功,使用expect-send模式发送:

模拟终端输入(成功!):

2.3.4.3编译运行and 生成文件

封装的Makefile如下:

解释如下:

  1. clean:【删除已有的gcda,gcno,gcov,避免出现重复时间戳timestamp导致报错(文末第四part有记录)】
    • rm -rf *.gcno: 删除所有.gcno文件。.gcno文件是由gcc在编译时创建的,它们包含了有关代码覆盖率的信息。
    • rm -rf *.gcda: 删除所有.gcda文件。.gcda文件是由gcc在编译时创建的,它们包含了实际的覆盖率数据。
    • rm -rf *.gcov: 删除所有.gcov文件。.gcov文件是由gcov工具创建的,它们包含了覆盖率报告的文本形式。
  2. 2html【同html,不过此处专门用作单元测试,便于检验各函数覆盖率及根据报错精准定位问题】:
    • g++ -o test421_2 test.cpp -fprofile-arcs -ftest-coverage -lgtest -lgtest_main -pthread: 使用g++编译test.cpp文件,生成可执行文件test421_2。编译选项包括启用代码覆盖率分析(-fprofile-arcs -ftest-coverage),以及链接gtest和gtest_main库(-lgtest -lgtest_main)。-pthread选项是为了支持多线程代码。
    • ./simulate_input.sh: 运行simulate_input.sh脚本来模拟终端输入。
    • ./2simulate.sh: 运行2simulate.sh脚本来模拟终端输入。
    • ./test421_2: 运行生成的可执行文件test421_2来执行测试。
    • gcov test421_2-test.gcda: 使用gcov工具来分析.gcda文件,生成覆盖率报告。
    • gcovr --html-details -o ./test421/t2.html: 使用gcovr工具来创建HTML覆盖率报告,并将其保存到./test421/t2.html文件中。

2.3.4.4部分函数【除去简单输出类】测试样例及覆盖率展示

注:大部分函数实现通过如下模式【执行函数—终端模拟交互】进行覆盖,重复性极高;故只展示部分有特殊细节的测试函数:

手动加入了输出,检测过程:

输出结果相同,即strcmp应该为0,进入对应分支,而实际没有;

发现是windows下和linux下编辑的文件带有不同的换行符:【不可打印符号,故输出无法查看】

使用 cat -A a.txt 查看:

输入: sed -i 's/\r$//' create_infomember.sh解决后:

关于expect-send模式的输入细节,在2种模拟下,发现必须expect终端输出才能对应,不然会提前输出,如下所示:【expect不到的情况

5) void modify_book()

同【执行函数—终端模拟交互】;

与【2.5】相似,此处的strcmp同样无法返回0;

注意expect-send中的输入:

有的输入无需“\r“【或者说不能有】,否则交互过程极易出错;

且同样有理论可达但实际不能进入的分支:

2.4测试命令:

逐条解释项目构建Makefile中包装的测试命令以及命令对应的输入输出,保证结果可复现。

Makefile及命令解释,详见2.3.4.3编译运行】部分;

关于.sh【即Bash交互】中,模拟输入输出约400行,其中代码见附件;大体框架为expect-send模拟;

测试用例约20个;近130行;主体是启动void函数及断言输出;

2.5测试结果:

2.5.1覆盖率结果:

注:有一个bug语句无法逻辑覆盖;

分支覆盖率是大部分被printf(),fclose()等基础功能函数拉低;

2.5.2未覆盖原因说明:

1)无法进入的分支:

【实际文件内,不可能没有书,即quantity永不为0】

2)printf()等函数大规模使用

一些默认库的功能函数,默认库实现时带有分支【具体见其实现如下】:

实际分支覆盖率为:705/739 = 95.4%【仍有部分个位数量级的函数分支实际不可达,此处未考虑】

3)程序质量分析:

质量存在较大问题,实际与用户交互中,问题较多,但总是以不可思议的方式继续运行;

输入错误输入,但仍然能运行【虽然是在错误处理下的继续运行,这时已经是逻辑错误了,运行下去也没有意义了】,有一定鲁棒性。

4)程序性能分析:

存在部分bug,但系统整体的鲁棒性较好,几乎覆盖了所有不同类型的输入【尤其是不合法的输入】。Bug部分示下:

潜在Bug1:跳转错误
潜在Bug2:关于分支判断条件的问题:

Check函数如下图所示:

可以看到,此中的strcmp()比较及参数带有很多问题:
1.编号是char数组,而非int类;

2.fscanf读入library.txt的图书数据,末尾没有加换行符以终止;

以上就导致,此分支永远无法进入,因为输入的number是”12\r”一类:

(WINDOWs下查看 library.txt)

(linux下cat -A 查看library.txt)

即实际的bookinfo[booknumber]是”1     ”带有很多空格而末尾不带有终止符,或是默认”\0”,导致strcmp永远不返回0,自然此分支无法覆盖;【此问题出现较多,分支覆盖达不到基本是因为此strcmp的问题】

同样,2.3.4.4_3) void delete_book() 也详细输出对比了两种情况。

改进意见:修改strcmp的定义,即自实现一个strcmp函数,忽略掉文末的不同终止符;实现正确比较。

其余见【2.3.4.4】函数测试实现中,详细记载了各类函数的bug及解决。

四、补充内容:

对源代码部分修改的说明:

1)由于输入输出极其多,为查看执行过程,将部分影响显示的清屏函数system(clear)注释掉;

2)由于部分分支是打开相应文件查看内容,总有分支是对于文件为空的情况,事实上该文本文件并不为空,实际情况下,此分支不会进入。此类分支占大多数,故分支覆盖率不高。

3)两个函数使用同一文本,而对其操作和写入的格式不一,导致此函数执行写入后下一函数打开即读取错误;此系统此错误难以解决,因为此共用文件不可缺少,唯一解决办法是修改其输入格式。

心得体会和经验收获:

算是对课上理论的一次实践。花费了很多时间,很多时间感觉是试错浪费掉了…但是不可否认的是在这些试错中学到了很多软件底层的知识,算是对很多知识的补全;在此作部分总结:

1)关于”\r”和“\n”

  1. \r (Carriage Return):
    • 将光标移动到行首而不创建新行。
  2. \n (New Line):
    • 在 Linux 系统中,\n用于创建新行。Linux默认行结束符。打印文本时,\n会确保文本在屏幕上创建一个新的文本行。

在不加转义符时,就会在错误组合下编码,如下:

“梦\n”  会打印为以上末尾的乱码;

2).sh模拟输入输出交互

发现此处等待很久:

查询后,设置超时时间:

3)补充

其余见【2.3.4.4部分函数测试】中,详细记载了各类函数的bug及解决。

以及见【三、各类报错及解决】中,关于试错与收获的详细记载。

  • 8
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值