C/C++单测覆盖率分析

前段时间,负责的CI平台有需求想作 C/C++ 单测覆盖率统计,以前只作过Java相关工做,没有接触过 C/C++ 的单元测试,通过一番折腾,搞了一个基本可用的方案,把分析过程记录下来,分享给你们。php

测试环境:html

  • OS XUbuntu 18.04.1 LTS
  • GCC 7.3.0-16ubuntu3
  • GoogleTest 1.8.1
  • LCOV 1.13
  1. 编译GoogleTest,运行gtest例子程序

从github克隆最新版GoogleTest;linux

执行cmake生成Makefile文件,gtest_build_samples参数表示构建gtest例子程序;c++

再执行make进行编译。git

git clone https://github.com/google/googletest.git
cd googletest
cmake -Dgtest_build_samples=ON .
make
复制代码

image.png
image.png

生成的gtest动态库文件在lib目录下: github

image.png
生成的测试例子程序在 googletest 目录中:
image.png
这些都是可执行文件,执行便可进行单元测试; 打印信息包括测试用例及其运行状况,测试结果统计等。
image.png

  1. 例子程序分析

以sample1为例,sample1.h 为头文件,sample1.cc为源码文件,sample1_unittest.cc为单元测试源码文件。ubuntu

能够看到sample1_unittest.cc引入了gtest库,使用TEST宏定义测试用例,使用EXPECT_XXX宏作断言检查。vim

gtest除了可使用TEST宏编写测试用例,还可使用继承 testing::Test 等类方式进行更灵活的配置,具体可参考其余例子程序如sample3_unittest.cc。bash

gtest支持多种方式编写测试用例,使用TEST宏是最简单的一种。工具

编写的测试用例想要执行,只须要在main方法中调用RUN_ALL_TESTS()便可,可参见gtest_main.cc文件。

TEST(FactorialTest, Positive) {
  EXPECT_EQ(1, Factorial(1));
  EXPECT_EQ(2, Factorial(2));
  EXPECT_EQ(6, Factorial(3));
  EXPECT_EQ(40320, Factorial(8));
}
复制代码
#include <cstdio>
#include "gtest/gtest.h"
#ifdef ARDUINO
void setup() {
  testing::InitGoogleTest();
}
void loop() { RUN_ALL_TESTS(); }
#else
GTEST_API_ int main(int argc, char **argv) {
  printf("Running main() from %s\n", __FILE__);
  testing::InitGoogleTest(&argc, argv);
  return RUN_ALL_TESTS();
}
#endif
复制代码
  1. 手动编译例子程序

使用gcc手动编译例子程序,以sample1为例。 目录结构以下,其中:

bin 为可执行程序目录

gtest 为gtest的头文件和库文件,include为gtest工程中的googletest/include目录,lib为gtest工程编译后的lib目录

obj 为目标文件目录

src 为源码文件目录,包括主程序main.cpp,头文件sample1.h,代码文件smaple1.cc,单测代码sample1_unittest.cc。

image.png
在gtest_demo目录下执行gcc命令,分别编译及连接各个文件

g++ -Igtest/include -c src/main.cpp -o obj/main.o
g++ -Igtest/include -c src/sample1.cc -o obj/sample1.o
g++ -Igtest/include -c src/sample1_unittest.cc -o obj/sample1_unittest.o
g++ -Lgtest/lib -o bin/gtest_demo obj/main.o obj/sample1.o obj/sample1_unittest.o gtest/lib/libgmock.a gtest/lib/libgmock_main.a gtest/lib/libgtest.a gtest/lib/libgtest_main.a
复制代码

在obj目录中生成目标文件main.o,sample1.o,sample1_unittest.o。 在bin目录中生成可执行文件 gtest_demo。

image.png
image.png
执行 gtest_demo 文件能够查看单元测试结果:
image.png

  1. 使用CMake构建例子程序

使用CMake构建例子程序,以sample1为例。

目录结构以下,其中:

build 为cmake构建目录

gtest 为gtest的头文件和库文件,include为gtest工程中的googletest/include目录,lib为gtest工程编译后的lib目录

src 为源码文件目录,包括主程序main.cpp,头文件sample1.h,代码文件smaple1.cc,单测代码sample1_unittest.cc

CMakeLists.txt 为cmake构建配置文件

image.png
编写CMakeLists.txt 文件

cmake_minimum_required(VERSION 2.8.8)
project(gtest_demo)
set( CMAKE_CXX_FLAGS "-std=c++11" )
set( CMAKE_BUILD_TYPE "Debug" )
include_directories( "gtest/include" )
link_directories("gtest/lib")
add_library(gtest_demo_lib "src/sample1.cc" "src/sample1_unittest.cc")
add_executable(gtest_demo "src/main.cpp" "src/sample1.cc" "src/sample1_unittest.cc")
target_link_libraries(gtest_demo gtest_demo_lib gtest)
复制代码

在build目录下执行 cmake .. ,生成Makefile文件;

执行make生成编译构建功能,生成可执行程序;

执行gtest_demo程序,便可获得单元测试结果。

image.png
image.png
image.png

  1. 使用LCOV统计覆盖率

LCOV 是GCOV的可视化工具,GCOV是linux代码覆盖率统计工具。

使用LCOV须要在编译是添加-fprofile-arcs -ftest-coverage参数。

g++ -fprofile-arcs -ftest-coverage -Igtest/include -c src/main.cpp -o obj/main.o
g++ -fprofile-arcs -ftest-coverage -Igtest/include -c src/sample1.cc -o obj/sample1.o
g++ -fprofile-arcs -ftest-coverage -Igtest/include -c src/sample1_unittest.cc -o obj/sample1_unittest.o
g++ -fprofile-arcs -ftest-coverage -Lgtest/lib -o bin/gtest_demo obj/main.o obj/sample1.o obj/sample1_unittest.o gtest/lib/libgmock.a gtest/lib/libgmock_main.a gtest/lib/libgtest.a gtest/lib/libgtest_main.a
复制代码

这样生成的目标文件目录中会生成对应的 gcno 文件

image.png
运行生成的可执行程序,gtest_demo,能够生成gcda文件;

执行lcov命令,生成代码覆盖率文件gtest_demo.info,使用--no-external参数能够忽略项目外部代码的统计;

执行genhtml命令,将覆盖率结果文件转换为html文件,并输出到指定目录。

bin/gtest_demo
lcov --capture --directory . --output-file gtest_demo.info --test-name gtest_demo --no-external
genhtml gtest_demo.info --output-directory output --title "JCI GoogleTest/LCOV Demo" --show-details --legend
复制代码

image.png
在outout目录生成最终的覆盖率结果
image.png
查看index.html文件,结果以下 能够点击连接查看每一个目录,每一个文件的覆盖率结果,包括行覆盖率(Line)和方法覆盖率(Function)
image.png
image.png

  1. 使用CMake集成LCOV工具

使用LCOV统计代码覆盖率,须要在在编译和连接时加上-fprofile-arcs -ftest-coverage参数;

所以修改CMakeLists.txt以下:

这样执行完 cmake & make 后,就会生成gcno文件。

再按【5】执行后面的命令,生成最终的覆盖率统计文件。

cmake_minimum_required(VERSION 2.8.8)
project(gtest_demo)
set( CMAKE_CXX_FLAGS "-std=c++11" )
set( CMAKE_BUILD_TYPE "Debug" )
 
# 添加下面三行,在编译和连接时会加上-fprofile-arcs -ftest-coverage参数
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage")
set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage -lgcov")
 
include_directories( "gtest/include" )
link_directories("gtest/lib")
add_library(gtest_demo_lib "src/sample1.cc" "src/sample1_unittest.cc")
add_executable(gtest_demo "src/main.cpp" "src/sample1.cc" "src/sample1_unittest.cc")
target_link_libraries(gtest_demo gtest_demo_lib gtest)
复制代码

覆盖率结果

image.png

  1. CI平台集成GoogleTest/LCOV

  • 编译机上需安装gtest、gcc、gcov、lcov等工具及依赖类库;
  • 须要工程根据gtest规范编写单元测试用例,须要提供依赖的gtest库;
  • gcc直接编译方式,能够按【5】中描述执行相关命令;
  • cmake方式,须要修改CMakeLists.txt文件,可使用option方式增长参数控制, CMakeLists.txt增长以下代码:
OPTION(ENABLE_GCOV "Enable gcov (debug, Linux builds only)" OFF)
IF (ENABLE_GCOV AND NOT WIN32 AND NOT APPLE)
  SET(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage")
  SET(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage")
  SET(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fprofile-arcs -ftest-coverage -lgcov")
ENDIF()
复制代码

而后就能够在执行cmake时经过DENABLE_GCOV参数控制是否启用覆盖率统计。

cmake . -DENABLE_GCOV=1

  • 执行命令所需参数: 可执行程序名,可执行程序输出目录,覆盖率结果输出目录等
  • 编写覆盖率结果解析程序,从index.html文件中解析出覆盖率结果
  1. 须要注意的地方

  • 遇到比较多的问题是gtest依赖的库,好比pthread
  • 报告中不包含branch覆盖率信息 lcov 1.10之后版本默认不包含branch coverage信息,须要经过修改 vim /etc/lcovrc 文件默认打开branch分支信息的输出,具体修改以下:
# Specify if branch coverage data should be collected andprocessed.
lcov_branch_coverage = 1 #去掉注释,值改成1
# Include branch coverage datadisplay (can be disabled by the --no-branch-coverage option of genhtml)
genhtml_branch_coverage = 1 #去掉注释,值改成1
复制代码

或者在lcov命令后加上参数 lcov -d <gcda目录位置> -b <测试代码路径> -c -o result.info --rc lcov_branch_coverage=1 再在genhtml命令后加上参数 genhtml -o result result.info --branch-coverage

  • lcov支持覆盖率维度包括行、方法、分支,和jacoco有必定差异,可使用方法维度进行卡点

  • 去除和提取指定文件 对于复杂项目,不想包含某个文件夹内的文件覆盖率信息,即反向去除不须要的文件,可使用 --remove 参数 lcov --remove all.info '/lib/' -o result.info 正向提取须要的文件,可使用 --extract 参数 lcov --extract all.info '/src/' -o result.info 注意:lcov 不容许同时使用--extract 和 --remove

  • lcov使用 --no-external 参数排除外部库

参考: www.hahack.com/codes/cmake…

github.com/google/goog…

ltp.sourceforge.net/coverage/lc…

相关文章
相关标签/搜索