1 相关工具方面
1.1 工具介绍
1.1.1 基础工具:gcov
gcov
是GCC的一个标准工具,随GCC一同发布。它用于分析与GCC编译器一起使用--coverage
(也就是-fprofile-arcs
和-ftest-coverage
组合)选项编译的程序的代码覆盖率数据。gcov
可以为各个源文件生成覆盖率报告,包括被执行的次数、哪些代码行被执行、哪些没有等信息。报告通常以纯文本的形式输出,但格式不利于直观理解或集成到其他工具。
1.1.12 高级工具
lcov
和gcovr
都是代码覆盖率工具,它们都是建立在GCC的gcov
覆盖率数据的基础上的。它们的主要目的是提供更易于理解的代码覆盖率报告,并支持更多种类的输出格式。
lcov:
-
lcov
是一个图形化的前端显示工具,用于GCC的gcov
。 -
它生成HTML报告,显示程序中实际运行的代码行数和总代码行数。
-
lcov
能够整合多个gcov
数据文件的信息,生成完整的项目覆盖率概览。 -
它支持过滤特定文件或目录,只提供有选区域的覆盖率信息。
-
lcov
倾向于使用在Linux系统中,并且和一些GUI应用程序(如genhtml
)一起使用来生成具有链接和图形的报告。 -
lcov
输出的HTML报告非常详细,包括源代码的颜色分布显示,易于直观地看到哪些代码被执行,哪些没有。
gcovr:
-
gcovr
提供更加丰富的输出选项,除了HTML报告,还支持XML、TXT、JSON等格式的报告,适用于多种用途。 -
gcovr
可以将gcov
数据转换为多种覆盖率工具可以理解的格式,如Cobertura格式的XML报告,这使得它能较容易地集成进持续集成系统,如Jenkins、TeamCity等。 -
它是一个命令行工具,易于在自动化脚本和构建流程中使用。
-
gcovr
也支持过滤和排除特定文件和目录。 -
相对于
lcov
的图形化HTML报告,gcovr
更加灵活和自动化,适合于需要在命令行环境中工作的场景。 -
gcovr
通常用于项目的持续集成过程中,而lcov
更多用于生成详细的代码覆盖率分析报告。
这会将lcov
安装到你的系统中。你可以通过键入lcov --version
来检查是否安装成功。
1.2软件安装
要在Ubuntu系统上安装lcov
和gcovr
,你可以使用apt
包管理器(对于lcov
而言)和pip
(即Python的包安装器,对于gcovr
而言)。
sudo apt update sudo apt install lcov
安装gcovr: gcovr
是用Python编写的,因此可以通过pip
来安装。首先,确保你的Ubuntu系统已安装pip
。如果没有安装pip
,你可以使用以下命令来安装它:
sudo apt update sudo apt install python3-pip
随后,通过pip
来安装gcovr
:
pip3 install gcovr
2 编译方面
2.1 编译选项
增加编译选项:--coverage -fprofile-arcs -fprofile-update=atomic -ftest-coverage
--coverage
: 这个选项是 -fprofile-arcs
和 -ftest-coverage
选项的组合。启用这个选项后,GCC 会生成额外的代码和数据用于覆盖率分析,通常与代码测试工具一同使用。具体来说,它会记录哪些代码被执行了,在测试完成后,可以用于生成覆盖率报告。
-fprofile-arcs
: 这个选项开启了生成程序执行时代码覆盖(profile)信息的功能。这个选项会在运行时收集程序的控制流转信息,用于之后的优化。它会给每个基本代码块(一个程序中的基本执行单元)添加额外的代码,以便记录每个代码块运行时被执行的次数。
-fprofile-update=atomic
: 这是 -fprofile-arcs
的补充选项,用于指定程序如何更新控制流转计数器。当你的程序运行在多线程环境中时,atomic
关键字会让GCC使用原子操作来更新这些计数器,防止因数据竞态导致计数不准确。原子操作确保了计数更新的安全性,不会被其他同时运行的线程打断,从而确保数据的完整性和一致性。
-ftest-coverage
:这个选项会在程序运行时收集关于哪些代码被执行以及执行频率的信息,这些信息通常用来生成测试的覆盖率报告,告诉你哪些代码在测试中被执行,哪些没有。
组合使用这些选项可以在测试过程中为每个函数、数据和源文件生成详细的覆盖率数据。进而,它也能在优化阶段指导编译器进行更有效的代码生成。
2.2 .gcno和.gcda文件
当使用GCC的-fprofile-arcs -ftest-coverage
选项编译源代码并运行时,它将为每个源文件生成两种类型的输出文件:.gcno
文件和.gcda
文件。
-
.gcno文件:这是在添加了
-fprofile-arcs
编译选项后,在源代码的编译阶段生成的,其中包含了被测程序的控制流图信息。主要用来存储程序中每个基本块的编号,以及这些基本块间的跳转关系等静态信息。每一个被编译的源文件(或者说编译单元)都对应生成一个.gcno文件。生成的.gcno文件与被测程序的编译过程是同步的,就是说,每当GCC编译完一个编译单元,就立即生成一个.gcno文件。 -
.gcda文件:这是添加了
-ftest-coverage
编译选项后,在源代码被实际执行阶段生成或更新的,其中包含了程序运行的动态信息,即覆盖信息,包括基本块的执行次数等。每一次执行被测程序,对应的.gcda文件就会被更新一次,如果被测程序没能正常结束(例如遇到段错误或者程序中有调用到exit()函数等情况),则对应的.gcda文件将不会被更新。
在创建代码覆盖率报告(例如使用工具gcov)时,这两种文件将被同时使用,基于.gcno文件的静态分支信息以及.gcda文件的动态执行次数信息,可以生成详尽的代码覆盖率报告。
总结一下,.gcno文件是在编译阶段生成的,记录了代码的结构信息,而.gcda文件是在程序实际运行时生成的,记录了代码的执行信息。这两种文件合并后可以用于创建详尽的代码覆盖率报告。
2.3 引入gtest库
步骤1:下载Google Test源码
你可以从Google Test的GitHub仓库下载源码:
比如我使用的是1.10.0版本的gtest
git clone https://github.com/google/googletest.git -b release-1.10.0
步骤2:在c++工程中添加gtest源码
在c++工程中新建一个third_party目录,该目录用于存放第三方源码和库。然后将上面步骤一中clone下来的整个工程复制到third_party目录下。
步骤3:修改CMakeLists.txt
# 引入google test工程,对其进行编译
add_subdirectory(${GOOGLETEST_SOURCE_DIR} EXCLUDE_FROM_ALL)
# 链接gtest库
target_link_libraries(${TARGET} gtest_main my_project_lib)
3 unitest工程
3.1 工程结构
通常针对一个.cpp文件或者一个模块,写一个对应的unitest.cpp文件,一个CMakeLists.txt文件,并最终生成一个可执行程序。
例如我的service模块中有一个ipc_service.cpp,我在test目录下也建一个service目录,里面新建ipc_service_unitst.cpp和一个CMakeLists.txt文件。
3.2 单元测试程序的编写
首先引入gtest库头文件,然后引入待测模块的头文件
然后编写test case函数,以TEST()或TEST_F()等命名的函数,这些都是gtest库的宏定义。
然后每一个单元测试可执行程序都对应一个main函数,就像普通的进程一样。其中的RUN_ALL_TESTS()也是一个gtest库中的宏,会调用该可执行文件中的所有的TEST()和TEST_F()等编写的test case函数。
#include <gtest/gtest.h> //gtest库头文件
#include "ipc_service.h" //待测模块的头文件
//test case
TEST(IPCServiceTest, InitTest){
IPCService serv;
//test for init ok
EXPECT_EQ(-1, serv.init(std::string());
//test for init error
EXPECT_EQ(0, serv.init(std::string("socket"));
}
//main函数
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
3.3 单元测试脚本
通常针对整个工程编写一个run_all_unitest.sh的脚本来执行项目中的所有的单元测试程序,项目中针对每个模块(或者.cpp)生成一个unitest可执行程序,通常这些可执行程序放在某个共同的目录下,run_all_unitest.sh脚本去遍历这个目录执行所有的可执行程序。
#!/bin/bash
#遍历unitest程序的存储路径
function run_all_unitests()
{
for file in "$1"/*;
do
if [ -x $1/$file ]; then
echo $1/$file
sh -c $1/$file
sh -c "$1/$file > /dev/null 2>&1"
fi
}
#执行函数
run_all_unitests ./
#创建覆盖率报告的存储路径
mkdir -p temp/coverage_report
#gcovr配置选项
gcovr --config gcovr.cfg --object-directory build/x64_Debug
#执行gcovr生成报告
gcovr -r . --config gcovr.cfg \
--html --html-details -o build/coverage_report/index.html \
--object-directory temp/x64_Debug \
--exclude-unreachable-branches \
--exclude-throw-branches
echo -e "\033[32m ---------------------------\033[0m"
echo -e "\033[32m Congratulations!!! All unit tests passed :) \033[0m"
echo -e "\033[32m ---------------------------\033[0m"
4 问题总结记录
4.1 问题记录
4.2 参考资料
68080 – gcov returns negative counts
An error about lambda was reported when executing gcovr · Issue #860 · gcovr/gcovr · GitHub
gcovr官网:gcovr — gcovr 6.0 documentation