代碼覆蓋率測試反映了測試的廣度與深度,量化了測試和開發質量,是十分有必要的,業界目前有針對各種語言的覆蓋率測試工具,本文主要介紹
C/C++
相關的覆蓋率測試工具Gcov
介紹
簡介
Gcov
是一個測試覆蓋程序,是集成在GCC
中的,隨GCC
一起發佈
基本概念
基本塊BB
基本塊指一段程序的第一條語句被執行過一次後,這段程序中的每一跳語句都需要執行一次,稱爲基本塊,因此基本塊中的所有語句的執行次數是相同的,一般由多個順序執行語句後邊跟一個跳轉語句組成
跳轉ARC
從一個BB
到另外一個BB
的跳轉叫做一個ARC
,要想知道程序中的每個語句和分支的執行次數,就必須知道每個BB
和ARC
的執行次數
程序流圖
如果把BB
作爲一個節點,這樣一個函數中的所有BB
就構成了一個有向圖,要想知道程序中的每個語句和分支的執行次數,就必須知道每個BB
和ARC
的執行次數,根據圖論可以知道有向圖中BB
的入度和出度是相同的,所以只要知道了部分的BB
或者ARC
大小,就可以推斷所有的大小,這裏選擇由ARC
的執行次數來推斷BB
的執行次數,所以對部分ARC
插樁,只要滿足可以統計出來所有的BB
和ARC
的執行次數即可
原理
測試程序首先進行編譯預處理,生成彙編文件,並完成插樁,插樁的過程中會向源文件的末尾插入一個靜態數組,數組的大小就是這個源文件中樁點的個數,數組的值就是樁點的執行次數,每個樁點插入3~4條彙編語句,直接插入生成的*.s
文件中,最後彙編文件經過彙編生成目標文件,在程序運行過程中樁點負責收集程序的執行信息
使用
編譯
測試代碼如下:
say.c
:
#include <stdio.h>
int say(char *what) {
printf("------ %s\n", what);
return 0;
}
main.c
#include <stdio.h>
extern int say(const char *);
int main(int argc, const char *argv[]) {
if (argv[1]) {
say("hello");
} else {
say("bye");
}
return 0;
}
添加-fprofile-arcs -ftest-coverage -fPIC
編譯參數編譯程序,生成可執行程序和*.gcno
文件,裏面記錄了行信息和程序流圖信息:
$ gcc -fprofile-arcs -ftest-coverage -fPIC -O0 say.c main.c
$ ls
a.out main.c main.gcno say.c say.gcno
數據收集
運行可執行文件,生成*.gcda
在默認生成在相應*.o
文件目錄,裏面記錄了*.c
文件中程序的執行情況,包括跳變次數等:
$ ./a.out
------ bye
$ ls
a.out main.c main.gcda main.gcno say.c say.gcda say.gcno
可以通過設置環境變量GCOV_PREFIX=/xxx/xxx
和GCOV_PREFIX_STRIP=x
來改變路徑,其中GCOV_PREFIX_STRIP
表示去掉源代碼路徑中的前幾級,默認爲0
,比如源代碼路徑爲/a/b/c/d.c
,GCOV_PREFIX_STRIP=2
,則實際使用的路徑是c/d.c
,如果GCOV_PREFIX=/e/f
,則.gcda
實際存放的路徑是/e/f/c/d.gcda
報告生成
針對某一個文件的執行情況,可以通過如下命令生成報告,並創建*.gcov
文件:
$ gcov -a main.c
File 'main.c'
Lines executed:80.00% of 5
Creating 'main.c.gcov'
常用選項,更多可參考Invoking gcov:
-b:分支覆蓋
-a:所有基本塊覆蓋
-f:函數覆蓋
注意事項
- 在編譯時不要加優化選項,否則代碼會發生變化,無法準確定位
- 代碼中複雜的宏,比如宏展開後是循環或者其他控制結構,可以用內聯函數來代替,因爲
gcov
只統計宏調用出現的那一行 - 代碼每一行最好只有一條語句
*.gcno
與*.gcda
需要匹配,兩個文件是有時間戳來記錄是不是匹配的- 若是編譯動態庫,需要在鏈接時
-lgcov
圖形化展示
gcov
生成的報告分散在各個源碼文件所對應的*.gcov
文件中,難以彙總分析,並且可視化效果較差,所以需要轉化成可視圖形化報告,有lcov
或gcovr
兩個工具可以完成,兩者功能基本相同,本文主要介紹gcovr
,是一個用Python
編寫的開源軟件,大小隻有幾十KB,安裝參見官網
列表形式
- 代碼覆蓋率
$ gcovr -r .
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File Lines Exec Cover Missing
------------------------------------------------------------------------------
main.c 5 4 80% 15
say.c 3 3 100%
------------------------------------------------------------------------------
TOTAL 8 7 87%
------------------------------------------------------------------------------
報告展示程序運行後覆蓋了80%
的代碼
- 分支覆蓋率
$ gcovr -b -r .
------------------------------------------------------------------------------
GCC Code Coverage Report
Directory: .
------------------------------------------------------------------------------
File Branches Taken Cover Missing
------------------------------------------------------------------------------
main.c 2 1 50% 14
say.c 0 0 --%
------------------------------------------------------------------------------
TOTAL 2 1 50%
------------------------------------------------------------------------------
報告展示了在main.c
中有一個分支沒有執行到
XML文件形式
$ gcovr --xml-pretty -r .
<?xml version="1.0" ?>
<!DOCTYPE coverage
SYSTEM 'http://cobertura.sourceforge.net/xml/coverage-04.dtd'>
<coverage branch-rate="0.5" branches-covered="1" branches-valid="2"
complexity="0.0" line-rate="0.875" lines-covered="7" lines-valid="8"
timestamp="1537930892" version="gcovr 3.4">
<sources>
<source>.</source>
</sources>
<packages>
<package branch-rate="0.5" complexity="0.0" line-rate="0.875" name="">
<classes>
<class branch-rate="0.5" complexity="0.0" filename="main.c"
line-rate="0.8" name="main_c">
<methods/>
<lines>
<line branch="false" hits="1" number="12"/>
<line branch="true" condition-coverage="50% (1/2)" hits="1" number="14">
<conditions>
<condition coverage="50%" number="0" type="jump"/>
</conditions>
</line>
<line branch="false" hits="0" number="15"/>
<line branch="false" hits="1" number="17"/>
<line branch="false" hits="1" number="19"/>
</lines>
</class>
<class branch-rate="0.0" complexity="0.0" filename="say.c" line-rate="1.0"
name="say_c">
<methods/>
<lines>
<line branch="false" hits="1" number="10"/>
<line branch="false" hits="1" number="11"/>
<line branch="false" hits="1" number="12"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>
HTML文件形式
$ gcovr -r . --html -o xxx.html
$ ls
a.out main.c main.gcda main.gcno say.c say.gcda say.gcno xxx.html
可以發現添加--html
參數後,可以生成html
文件,用瀏覽器打開,如下圖:
還可以添加--html-details
選項,爲每個代碼文件單獨生成html
$ gcovr -r . --html --html-details -o xxx.html
$ ls
a.out main.c main.gcda main.gcno say.c say.gcda say.gcno xxx.html xxx.main.c.html xxx.say.c.html
可以發現多了xxx.main.c.html
和xxx.say.c.html
,用瀏覽器打開xxx.html
,如下圖:
文件名較之前帶上了下劃線,單擊文件名,可以看到具體的代碼覆蓋情況,如下圖:
其它
其它功能,如Filters
等,可以參考官方文檔
Reference
About me
- GitHub:AnSwErYWJ
- Blog:http://www.answerywj.com
- Email:[email protected]
- Weibo:@AnSwEr不是答案
- CSDN:AnSwEr不是答案的專欄
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
本作品採用知識共享署名-相同方式共享 4.0 國際許可協議進行許可。