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…

相關文章
相關標籤/搜索