最近做了一系列的單元測試相關的工作,除了各種規范及測試框架以外,討論比較多的就是關于代碼覆蓋率的產生,c/c++與其他的一些高級語言或者腳本語言相比較而言,例如 Java、.Net和php/python/perl/shell等,由于沒有這些高級語言和腳本語言的反射的特性,其代碼覆蓋率的產生過程會稍微復雜一些。發現許多同學對C++的覆蓋率如何產生在都不太清楚,這里做一個簡單的介紹。
一、基本使用方法
在Linux上的c/c++開發一般都使用gcc/g++作為主要的編譯器,如果需要產生覆蓋率數據需要在Makefile或者Scons文件中做下面的編譯鏈接設置,
編譯的時候,增加 -fprofile-arcs -ftest-coverage 或者 –coverage;
鏈接的時候,增加 -fprofile-arcs 或者 –lgcov;
打開–g3 選項,去掉-O2以上級別的代碼優化選項;否則編譯器會對代碼做一些優化,例如行合并,從而影響行覆蓋率結果;
基本要求就上面三點,但有一個建議,為了上述幾個編譯選項的使用不影響到正常的編譯過程(否則會極大地影響程序的運行效率)。在使用makefile中通過參數傳遞來支持覆蓋率產生,可以在makefile使用下面的方式,
ifeq ($(coverage), yes)
CXXFLAGS += -fprofile-arcs -ftest-coverage
LINKERCXX += -fprofile-arcs -ftest-coverage
OPT_FLAGS = -g3
endif
這樣,可以使用 make coverage=yes 來引入這些編譯選項而不會影響到正常的編譯(scons同理)。
二、簡單示例
這里寫了一個簡單的程序做測試,主要包含三個文件:Rectangle.cpp, RectangleTest.cpp, Makefile。
1)Rectangle.cpp 是被測代碼,里面定義了一個簡單的類Rectangle(長方形),里面有三個方法:
set_values(),設置長方形對象的長和寬;
area(),求長方形的面積;
lenth(),求長放形的周長;
2)RectangleTest.cpp 是一個簡單的測試程序,為了demo使用,并沒有使用cppunit/gtest這樣的單元測試框架,直接使用了main()函數來調用Rectangle里面的方法;
Rectangle.cpp和RectangleTest.cpp的代碼如下圖,
3)Makefile比較簡單,主要支持在coverage=yes的參數支持。 可以使用-fprofile-arcs -ftest-coverage 選項,這里為了簡化使用了 –coverage。
覆蓋率產生的過程如下面四個步驟所示,其中步驟3和4,根據需要使用其中一種即可。
1. 編譯鏈接帶覆蓋率參數的源代碼;
2. 運行測試程序;
3. 使用gcov獲取文本形式的覆蓋率數據;
4. 使用lcov獲取html形式的覆蓋率數據;
下面針對本例,做這一過程的逐步演示。
1. 編譯鏈接帶覆蓋率參數的源代碼;
由于Makeifle中已經支持了coverage=yes選項,直接運行 “make coverage=yes”,這個時候會產生測試程序,并同時生成gcno文件(關于gcno文件的詳細解釋,參見第三部分背后原理),如下圖,
2. 運行測試程序;
運行./RectangleTest 測試程序,運行結束后,會針對所有的cpp源代碼文件產生相應的*.gcda文件(關于gcda文件的詳細解釋,參見第三部分背后原理),如下圖
3. 使用gcov獲取文本形式的覆蓋率數據;
需要注意的是,這個步驟不是必須的,如果需要文本格式(*.gcov)的覆蓋率結果,可是走這個步驟。如果想看html格式的結果,直接跳過這一步驟。gcov是gcc自帶的覆蓋率結果產生工具,無需單獨安裝。
針對某個源代碼文件,例如 Rectangle.cpp,執行”gcov Rectangle.cpp” 會產生Rectangle.cpp.gcov文件。
這是一個存文本文件,可以通過vim打開,看到詳細的行覆蓋率數據,如下
4. 使用lcov獲取html形式的覆蓋率數據;
有些時候需要使用html結果的數據展示,這樣看起來更加直觀一些。IBM開源了lcov這個工具,更多參見 http://ltp.sourceforge.net/coverage/lcov.php
工具使用,如下圖,
手動把cc_result目錄拷貝到http/apache等服務器的htdocs目錄下,可以通過瀏覽器來查看覆蓋率結果,如下,
整個覆蓋率生成的流程按照上面四個步驟就可以搞定。下面一節對其原理做簡單的闡述。
三、基本原理
1. 術語解釋
在了解背后原理之前,需要對覆蓋率技術的一些概念有簡單的了解。主要是基本塊(Basic Block),基本塊圖(Basic Block Graph),行覆蓋率(line coverage), 分支覆蓋率(branch coverage)等。
基本塊(Basic Block),”A basic block is a sequence of instructions with only entry and only one exit. If any one of the instructions are executed, they will all be executed, and in sequence from first to last.” 這里可以把基本塊看成一行整體的代碼,基本塊內的代碼是線性的,要不全部運行,要不都不運行;