C之(14)gcov覆盖率
C之(14)gcov覆盖率
Author: Once Day Date: 2024年12月30日
一位热衷于Linux学习和开发的菜鸟,试图谱写一场冒险之旅,也许终点只是一场白日梦…
漫漫长路,有人对你微笑过嘛…
全系列文章可参考专栏: C语言_Once-Day的博客-CSDN博客
前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。人工智能学习网站 - 点击跳转。
参考文章:
- gcov代码覆盖率测试-原理和实践总结-CSDN博客
- 使用 Gcov 和 LCOV 度量 C/C++ 项目的代码覆盖率 - 知乎
C之(14)gcov覆盖率
Author: Once Day Date:2024年12月30日
漫漫长路,有人对你微笑过嘛…
参考引用文档:
- gcov代码覆盖率测试-原理和实践总结-CSDN博客
- 使用 Gcov 和 LCOV 度量 C/C++ 项目的代码覆盖率 - 知乎
文章目录
- C之(14)gcov覆盖率
- C之(14)gcov覆盖率
- 1. 介绍
- 1.1 背景
- 1.2 gcov工作流程
- 1.3 gcov工作原理
- 2. 实践
- 2.1 编译源码
- 2.2 运行二进制文件
- 2.3 生成覆盖率测试数据
1. 介绍
1.1 背景
在现代软件开发过程中,代码质量和可靠性至关重要。为了确保软件的正确性和稳定性,软件测试已成为开发流程中不可或缺的一部分。而在众多软件测试方法中,代码覆盖率测试因其客观、量化的特点而备受关注。
代码覆盖率测试是一种白盒测试方法,旨在衡量测试用例对代码的执行覆盖程度。通过插桩等技术手段,代码覆盖率测试工具可以记录并统计测试过程中每一条代码的执行情况,并以此计算出多种覆盖率指标,如语句覆盖率、分支覆盖率、条件覆盖率等。这些指标从不同维度反映了代码的测试充分性,为发现未被测试覆盖的代码片段提供了量化依据。
在代码覆盖率测试领域,Gcov是一款功能强大且被广泛使用的工具。Gcov由GNU Compiler Collection (GCC)提供,能够与GCC编译器无缝集成,支持C、C++等多种编程语言。通过在编译时添加特定参数,Gcov可以在程序执行过程中实时记录代码的执行频率,并在程序结束后生成详细的覆盖率报告。
Gcov的输出报告包含了每个源文件的执行概况,以及每一行代码的执行次数。同时,Gcov还提供了多种格式的输出,如HTML、XML等,便于用户查看和分析覆盖率数据。凭借其易用性和与GCC的紧密集成,Gcov已成为许多开发者进行代码覆盖率测试的首选工具。
除Gcov外,还有许多其他优秀的代码覆盖率测试工具,如:
-
LCOV,一款基于Gcov的图形化前端工具,能够生成更加直观和交互式的覆盖率报告。
-
Clover,一款适用于Java语言的代码覆盖率测试工具,提供了丰富的报告格式和与主流构建工具的集成。
-
Coverage.py,一款用于Python语言的代码覆盖率测试工具,支持分支覆盖率和HTML报告生成。
-
Istanbul,一款用于JavaScript语言的代码覆盖率测试工具,可以与主流的测试框架(如Mocha、Jasmine等)集成使用。
这些工具各有特色,为不同编程语言和开发环境下的代码覆盖率测试提供了便利。
1.2 gcov工作流程
图片来自文章:gcov代码覆盖率测试-原理和实践总结-CSDN博客
在使用Gcov进行代码覆盖率测试之前,首先需要对源代码进行特殊的编译处理。通常,这是通过在GCC编译器的编译选项中添加额外的参数来实现的。具体而言,需要在编译命令中加入"-fprofile-arcs"和"-ftest-coverage"两个选项。这两个选项的作用如下:
"-fprofile-arcs"
:该选项告知编译器在编译过程中插入必要的代码,用于记录程序执行流程和收集覆盖率数据。"-ftest-coverage"
:该选项指示编译器生成专门用于覆盖率测试的辅助信息文件。
添加了这两个编译选项后,编译器会在编译过程中对源代码进行插桩,为后续的覆盖率数据收集做好准备。
在源码编译阶段,编译器会在目标代码中插入特殊的汇编指令,称为"插桩"。这些插桩代码的作用是在程序执行过程中记录代码的执行情况,并将相关数据写入辅助文件中。
插桩代码通常包括以下几个部分:
- 在每个基本块的开头插入计数器,用于记录该基本块的执行次数。
- 在每个函数的入口和出口处插入代码,用于记录函数的调用次数和返回情况。
- 在程序的开始和结束位置插入代码,用于初始化和写入覆盖率数据文件。
通过插桩,Gcov可以精确地跟踪代码的执行流程,为后续的覆盖率分析提供必要的数据基础。
在编译过程中,编译器除了生成目标代码文件外,还会生成一个以".gcno"为后缀的辅助文件。这个文件包含了程序的静态结构信息,如函数定义、行号、基本块等。
.gcno文件的作用是为后续的覆盖率数据收集提供必要的上下文信息。当程序执行时,Gcov会根据.gcno文件中的信息,将收集到的覆盖率数据与源代码的结构进行关联,从而生成详细的覆盖率报告。
在编译并插桩后,程序就可以正常执行了。在执行过程中,之前插入的插桩代码会随着程序流程的进行而被触发,并将相关的执行数据写入到一个以".gcda"为后缀的文件中。
.gcda文件包含了程序运行过程中收集到的动态覆盖率数据,如每个基本块的执行次数、每个函数的调用次数等。这些数据是Gcov进行覆盖率分析的核心依据。
需要注意的是,.gcda文件是在程序执行结束时才会被写入磁盘。因此,为了获取完整的覆盖率数据,必须确保程序能够正常结束,并且对应的.gcda文件能够成功生成。
当程序执行完毕,并生成了.gcda文件后,就可以使用Gcov工具来分析覆盖率数据,并生成详细的覆盖率报告了。
通过运行"gcov"命令,并指定要分析的源代码文件,Gcov会自动读取对应的.gcno和.gcda文件,并将其中的信息进行综合分析。通常包括以下几个方面:
- 每个源文件的总体执行情况,如执行的行数、未执行的行数、覆盖率等。
- 每个函数的执行情况,包括函数的调用次数、行覆盖率等。
- 每一行代码的执行次数,以及未被执行的代码行。
Gcov会将分析结果以文本格式输出,同时也支持生成HTML格式的报告,便于用户直观地查看和分析覆盖率信息。
通过对覆盖率报告的分析,开发者可以发现代码中的薄弱环节,如未被测试覆盖的代码块、执行频率较低的函数等,并据此改进测试用例和代码质量。
1.3 gcov工作原理
基本块(Basic Block, BB)是Gcov进行覆盖率分析的基本单位。一个基本块是指一组顺序执行的代码指令,它们只有一个入口和一个出口。换句话说,一个基本块中的指令要么全部执行,要么全部不执行。
在Gcov的工作过程中,编译器会将源代码划分为若干个基本块。每个基本块都会被赋予一个唯一的标识号,用于后续的覆盖率数据收集和分析。
跳转(ARC)是指在基本块之间的控制流转移。当程序执行完一个基本块后,根据条件判断或函数调用等因素,会选择下一个要执行的基本块,这个过程就称为跳转。
在Gcov中,跳转是连接基本块的重要概念。通过跟踪和记录跳转的执行情况,Gcov可以构建出程序的动态执行流程,并据此计算覆盖率指标。
**程序流图(Control Flow Graph, CFG)**是一种表示程序控制流的有向图。在程序流图中,每个节点表示一个基本块,而有向边则表示基本块之间的跳转关系。
通过构建程序流图,Gcov可以直观地表示程序的静态结构和动态执行路径。在进行覆盖率分析时,Gcov会根据程序流图和收集到的执行数据,计算出各个基本块和跳转的执行次数,并生成相应的覆盖率报告。
为了实现覆盖率数据的收集,Gcov在编译阶段会对源代码进行插桩。插桩是一种代码插入技术,它在不影响程序原有逻辑的前提下,在特定位置插入额外的代码,用于记录程序的执行情况。
在Gcov中,插桩主要分为以下两类:
-
基本块计数器:在每个基本块的开头,Gcov会插入一个计数器,用于记录该基本块的执行次数。当程序执行到该基本块时,计数器就会自动加1。
-
跳转计数器:对于每个跳转(ARC),Gcov会插入一个计数器,用于记录该跳转的执行次数。当程序执行到相应的跳转语句时,计数器就会自动加1。
通过插桩,Gcov可以在程序运行过程中动态地更新基本块和跳转的执行计数。这些计数信息会被写入到.gcda文件中,供后续的覆盖率分析使用。
在收集到程序的执行计数数据后,Gcov就可以计算各种覆盖率指标了。常见的覆盖率指标包括:
-
语句覆盖率:表示被执行过的代码行数占总代码行数的比例。
-
分支覆盖率:表示被执行过的条件分支数占总条件分支数的比例。
-
路径覆盖率:表示被执行过的路径数占总可能路径数的比例。
Gcov会根据基本块和跳转的执行计数,结合程序流图的结构信息,自动计算出这些覆盖率指标。通过分析覆盖率指标,开发者可以全面了解测试用例对代码的覆盖情况,发现未被充分测试的代码区域,并有针对性地改进测试策略。
2. 实践
2.1 编译源码
下面以cmake脚本为例,makefile之类脚本都是类似的,因为只需要在CFLAGS加上--coverage
即可。
当在编译命令中添加--coverage
选项时,GCC编译器会自动启用生成覆盖率数据所需的插桩代码和辅助文件。
-fprofile-arcs
:该选项指示编译器在编译过程中插入必要的代码,用于记录程序执行流程和收集覆盖率数据。它会在每个基本块的开头插入计数器,并在函数的入口和出口处插入代码,以跟踪函数的调用次数和返回情况。-ftest-coverage
:该选项告知编译器生成专门用于覆盖率测试的辅助信息文件,如.gcno文件。这些文件包含了程序的静态结构信息,如函数定义、行号、基本块等,为后续的覆盖率数据收集和分析提供必要的上下文。
# 启用覆盖率测试
option(COVERAGE "Enable coverage reporting" OFF)
if(COVERAGE)
list(APPEND CXX_FLAGS "--coverage")
list(APPEND CXX_FLAGS -fprofile-dir=${CMAKE_CURRENT_SOURCE_DIR}/output/coverage)
list(APPEND C_FLAGS "--coverage")
endif()
-fprofile-dir
参数用于指定Gcov生成的覆盖率数据文件(.gcda)和辅助信息文件(.gcno)的存储目录。默认情况下,这些文件会被存储在与源代码文件相同的目录下。但是,通过使用-fprofile-dir
参数,我们可以将这些文件存储到一个单独的目录中,以便更好地组织和管理覆盖率数据。
-fprofile-dir
参数的语法如下:
-fprofile-dir=<directory>
其中,<directory>
表示要存储覆盖率数据文件和辅助信息文件的目录路径。该路径可以是绝对路径或相对路径。
举个例子,如果我们想将覆盖率数据文件存储到当前目录下的"coverage_data"子目录中,可以使用以下编译选项:
-fprofile-dir=./coverage_data
当使用-fprofile-dir
参数指定了存储目录后,Gcov生成的覆盖率数据文件(.gcda)和辅助信息文件(.gcno)的命名格式也会发生相应的变化。
对于.gcda文件,其命名格式为:
<source_file_name>.<extension>.gcda
其中,<source_file_name>
表示对应的源代码文件名(不包括路径),<extension>
表示源代码文件的扩展名。
例如,如果源代码文件为"main.cpp",则对应的.gcda文件名将是:
main.cpp.gcda
2.2 运行二进制文件
编译成功后,运行单元测试程序,gcda文件输出在指定的目录中,如下所示:
ubuntu->ANMK_netdog:$ ./output/bin/anmk-base-tests
......
ubuntu->ANMK_netdog:$ ll output/coverage
total 472
drwxrwxr-x 2 ubuntu ubuntu 12288 Dec 30 22:57 ./
drwxrwxr-x 6 ubuntu ubuntu 4096 Sep 5 14:46 ../
-rw-rw-r-- 1 ubuntu ubuntu 88 Dec 30 22:57 '#home#ubuntu#ANMK_netdog#unwind.cpp.gcda'
-rw-rw-r-- 1 ubuntu ubuntu 68 Dec 30 22:57 '#home#ubuntu#ANMK_netdog#alias.cpp.gcda'
-rw-rw-r-- 1 ubuntu ubuntu 5640 Dec 30 22:57 '#home#ubuntu#ANMK_netdog#alias_unittest.cpp.gcda'
......
2.3 生成覆盖率测试数据
LCOV(Linux Code Coverage)是一个基于Gcov的图形化前端工具,它可以收集和处理Gcov生成的覆盖率数据,并生成易于阅读和导航的HTML格式覆盖率报告。LCOV提供了一组命令行工具,用于执行各种覆盖率数据的操作,如数据捕获、过滤、合并和生成报告等。
在使用lcov工具之前,需要对gcda文件的名字进行转换,去掉前缀路径:
#!/usr/bin/env python3
# 将 xxx#xxx#file.gcda 文件名修改为 file.gcda
import os
import sys
def gcov_file_deal(path):
for root, dirs, files in os.walk(path):
for file in files:
if file.endswith('.gcda'):
new_file = file.split('#')[-1]
os.rename(os.path.join(root, file), os.path.join(root, new_file))
print(f'{file} -> {new_file}')
if __name__ == '__main__':
if len(sys.argv) < 2:
print(f'Usage: {sys.argv[0]} <path>')
sys.exit(1)
gcov_file_deal(sys.argv[1])
执行改脚本后,gcda文件的名字都标准化了:
ubuntu->ANMK_netdog:$ ./gcov_file_deal.py output/coverage
#home#ubuntu#ANMK_netdog#unwind.cpp.gcda -> unwind.cpp.gcda
......
然后从源码编译输出里面收集gcno文件,如下所示:
ubuntu->ANMK_netdog:$ find build/debug-cpp11/ -name "*.gcno" -exec cp -v {} output/coverage \;
'build/strings/stringprintf_unittest.cpp.gcno' -> 'output/coverage/stringprintf_unittest.cpp.gcno'
'build/strings/string_util_constants.cpp.gcno' -> 'output/coverage/string_util_constants.cpp.gcno'
捕获覆盖率数据后,我们可以使用genhtml工具将.info文件转换为可视化的HTML报告,例如:
genhtml output/coverage/coverage.info --output-directory output/coverage/
其中,--output-directory
选项指定了生成HTML报告的输出目录。
然后可以打包文件,放在网页服务器下进行查看,如下所示: