Linux下c/c++項目代碼覆蓋率的產生方法

最近做了一系列的單元測試相關的工作,除了各種規范及測試框架以外,討論比較多的就是關于代碼覆蓋率的產生,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.” ?這里可以把基本塊看成一行整體的代碼,基本塊內的代碼是線性的,要不全部運行,要不都不運行;
  • 基本塊圖(Basic Block Graph),基本塊的最后一條語句一般都要跳轉,否則后面一條語句也會被計算為基本塊的一部分。?如果跳轉語句是有條件的,就產生了一個分支(arc),該基本塊就有兩個基本塊作為目的地。如果把每個基本塊當作一個節點,那么一個函數中的所有基本塊就構成了一個有向圖,稱之為基本塊圖(Basic Block Graph)。且只要知道圖中部分BB或arc的執行次數就可以推算出所有的BB和所有的arc的執行次數;
  • 打樁,意思是在有效的基本塊之間增加計數器,計算該基本塊被運行的次數;打樁的位置都是在基本塊圖的有效邊上;
  • 行覆蓋率(line coverage),源代碼有效行數與被執行的代碼行的比率;
  • 分支覆蓋率(branch coverage),有判定語句的地方都會出現2個分支,整個程序經過的分支與所有分支的比率是分支覆蓋率。注意,與條件覆蓋率(condition coverage)有細微差別,條件覆蓋率在判定語句的組合上有更細的劃分。
2. ?gcc/g++ 編譯選項

gcc需要靜態注入目標程序編譯選項,在編譯鏈接的時候加入2個選項(-ftest-coverage?-fprofile-arcs?),編譯結束之后會生成 *.gcno 文件,而經過靜態注入的目標程序在“正常結束”后,會在運行目錄下產生*.gcda數據文件,通過gcov工具就可產生覆蓋率數據結果。

-ftest-coverage

Produce a notes file that the gcov code-coverage utility (see gcov—a Test Coverage Program) can use to show program coverage. Each source file’s note file is called auxname.gcno. Refer to the -fprofile-arcs option above for a description of auxname and instructions on how to generate test coverage data. Coverage data matches the source files more closely if you do not optimize.
讓編譯器生成與源代碼同名的.gcno文件(note file),這種文件含有重建基本塊依賴圖和將源代碼關聯至基本塊的必要信息;

?

-fprofile-arcs

Add code so that program flow arcs are instrumented. During execution the program records how many times each branch and call is executed and how many times it is taken or returns. When the compiled program exits it saves this data to a file called auxname.gcda for each source file. The data may be used for profile-directed optimizations (-fbranch-probabilities), or for test coverage analysis (-ftest-coverage). Each object file’s auxname is generated from the name of the output file, if explicitly specified and it is not the final executable, otherwise it is the basename of the source file. In both cases any suffix is removed (e.g. foo.gcda for input file dir/foo.c, ordir/foo.gcda for output file specified as -o dir/foo.o). See Cross-profiling.

讓編譯器靜態注入對每個源代碼行關聯的計數器進行操作的代碼,并在鏈接階段鏈入經態度libgcov.a,其中包含在程序正常結束時生成*.gcda文件的邏輯;

下面通過源碼解析來說明到底這2個選項做了什么。通過g++ -S選項,產生匯編語言Rectangle.s 和?Rectangle_cc.s?(增加–coverage選項),命令如下,

g++ -c -o Rectangle.s Rectangle.cpp -g -Wall -S

g++ -c -o Rectangle_cc.s Rectangle.cpp -g -Wall –coverage -S

vimdiff?Rectangle.s 和?Rectangle_cc.s,如下圖


通過這樣匯編語言的對比,可以看出gcc通過這2個參數,把打樁的過程完成了。

更深入的內容,例如,如果想知道gcno/gcda文件的格式,可以參考 @livelylittlefish?的一篇文章,GCC Coverage代碼分析-.gcda/.gcno文件及其格式分析(http://blog.csdn.net/livelylittlefish/article/details/6448885)。

四、擴展話題

通過上面三部分的介紹,相信絕大多數覆蓋率問題都可以解決,下面2個問題是我們在實際運行過程中遇到的,也分享一下。

  1. 覆蓋率的結果只有被測試到的文件會被顯示,并非所有被編譯的代碼都被作為覆蓋率的分母

實際上,可以看到整個覆蓋率的產生的過程是4個步驟的流程,一般都通過外圍腳本,或者makefile/shell/python來把整個過程自動化。2個思路去解決這個問題,都是通過外圍的偽裝。第一個,就是修改lcov的 app.info ,中間文件,找到其他的文件與覆蓋率信息的地方,結合makefile,把所有被編譯過的源程序檢查是否存于 app.info 中,如果沒有,增加進去。第二個偽裝,是偽裝 *.gcda,沒有一些源碼覆蓋率信息的原因就是該文件沒有被調用到,沒有響應的gcda文件產生。toast(http://toast.taobao.org/)是通過第一種偽裝來實現的,更多了解需要去看下開源代碼。

2. 后臺進程的覆蓋率數據收集;

其實上述覆蓋率信息的產生,不僅可以針對單元測試,對于功能測試同樣適用。但功能測試,一般linux下c/c++都是實現了某個Daemon進程,而覆蓋率產生的條件是程序需要正常退出,即用戶代碼調用 exit?正常結束時,gcov_exit 函數才得到調用,其繼續調用 __gcov_flush 函數輸出統計數據到 *.gcda 文件中。同樣2個思路可以解決這個問題,

第一,給被測程序增加一個 signal handler,攔截 SIGHUP、SIGINT、SIGQUIT、SIGTERM 等常見強制退出信號,并在 signal handler 中主動調用 exit 或 __gcov_flush 函數輸出統計結果。但這個需要修改被測程序。這個也是我們之前的通用做法。但參加過清無同學的一個講座后,發現了下面第二種更好的方法。

第二,借用動態庫預加載技術和 gcc 擴展的 constructor 屬性,我們可以將 signalhandler 和其注冊過程都封裝到一個獨立的動態庫中,并在預加載動態庫時實現信號攔截注冊。這樣,就可以簡單地通過如下命令行來實現異常退出時的統計結果輸出了。

五、其他編程語言

在我們的工程實踐中,還有其他的編程語言,都涉及到覆蓋率的產生,我們的工程實踐推薦下面的方法,
  • c/c++, ?本文介紹的方法;
  • Java, ?Maven??cobertura?插件;
  • Python,?PyUnit?+??coverage.py;
  • Php, phpunit +??–coverage-html?;
  • Perl,? Test::Class 和 Devel::Cover;
  • Shell,??shUnit2 + shcov;

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/287445.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/287445.shtml
英文地址,請注明出處:http://en.pswp.cn/news/287445.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C# WPF從后臺代碼生成行列可變的表格

z概述WPF常用的表格控件是DataGrid,這個控件在前臺XAML編寫的話,一般列已經固定,然后給每個列去綁定數據,但是如果我的列不固定,隨著運算結果變動呢?這時候DataGrid,就比較難實現這個需求&#…

軟件架構實踐文章鏈接

2019獨角獸企業重金招聘Python工程師標準>>> 架構 InfoQ: 又拍網架構中的分庫設計 SNS網站數據庫技術分析 - 51CTO.COM 數據庫水平切分的實現原理解析 - iBATIS - Java - JavaEye論壇 基于amoeba的mysql分布式數據庫學習(一) - Java - JavaEy…

【看動漫學編程】程序員在異世界生個娃 第2篇:外掛已準備就緒

前言 作者文筆比較水,還請見諒。 以下內容還將使用視頻動態漫畫表現,剪輯完將會貼出鏈接。 小說劇情為劇情需要,過渡到知識點,部分篇幅可能沒有技術知識點還望諒解。 由于沒有經費支持,所以畫出來的東西是我自己用代碼…

java剪切txt文件_用Java把剪切板的內容實時保存到txt

test類:提示用戶程序已啟動,提示保存位置,清空剪切板。package com.ariya.service;import com.ariya.service.impl.ClipboardServiceImpl;/*** author Ariya* 程序入口*/public class Test {public static void main(String[] args) {Clipboa…

【三維激光掃描】第一章:三維激光掃描入門基礎知識

隨著地理空間信息服務產業的快速發展,地理空間數據的要求越來越高。對地理空間數據的要求正朝著大信息量、高精度、可視化和可挖掘方向發展。地面激光雷達技術是一門新興的測繪技術,已逐漸成為廣大科研和工程技術人員全新的解決問題的手段。地面三維激光掃描技術與全站儀測量…

Android之kotlin里面本地圖片BitmapFactory.decodeFile轉bitmap失敗問題

1 問題 我們手機本地有個圖片文件比如如下 /storage/emulated/0/Android/data/package_name/cache/1586444511539.png 我們需要png轉bitmap,然后設置到ImageView里面顯示 var bitmap BitmapFactory.decodeFile(imagePath);if (bitmap null) returnelse mImagevi…

3、面向對象-繼承-多態

1、繼承子類可以繼承父類的一切,一個子類只能有一個父類,一個父類可以有多個子類//父類class Ren{public $name;public $sex;public $yuyan;function Say() {echo $this->name."正在講話!";}}//美國人的子類class America ex…

整理iOS9適配中出現的坑

一、NSAppTransportSecurity iOS9讓所有的HTTP默認使用了HTTPS,原來的HTTP協議傳輸都改成TLS1.2協議進行傳輸。直接造成的情況就是App發請求的時候彈出網絡無法連接。解決辦法就是在項目的info.plist 文件里加上如下節點: NSAppTransportSecurity - NSAl…

由c# dynamic是否裝箱引發的思考

前言前幾天在技術群里看到有同學在討論關于dynamic是否會存在裝箱拆箱的問題,我當時第一想法是"會"。至于為啥會有很多人有這種疑問,主要是因為覺得dynamic可能是因為有點特殊,因為它被稱為動態類型,可能是因為這里的動態對大家造成的誤解,認為…

【看動漫學編程】程序員在異世界生個娃 第3篇:搞不好我就是個王者

前言 作者文筆比較水,還請見諒。 以下內容還將使用視頻動態漫畫表現,剪輯完將會貼出鏈接。 小說劇情為劇情需要,過渡到知識點,部分篇幅可能沒有技術知識點還望諒解。 由于沒有經費支持,所以畫出來的東西是我自己用代碼…

PHP會話控制考察點

為什么要使用會話控制技術 HTTP協議是無狀態的,也就是說HTTP沒有一個內建的機制來維護兩個事務之間的狀態。當一個用戶完成一個請求發起第二個請求的時候,服務器無法知道這次請求是來自于上一次的客戶。而用戶登錄、購物車等,這些是需要服務器…

數據庫SQL語句學習筆記(6)-使用函數處理數據

1.SQL也可以用函數來處理數據,函數一般是在數據上執行的,為數據的轉換和處理提供了方便。但是每一個數據庫管理系統(DBMS)都有特定的函數,事實上,只有少數幾個函數被所有的DBMS等同地支持。例如&#xff0c…

java3n 1_1005 繼續(3n+1)猜想(JAVA)

卡拉茲(Callatz)猜想已經在1001中給出了描述。在這個題目里,情況稍微有些復雜。當我們驗證卡拉茲猜想的時候,為了避免重復計算,可以記錄下遞推過程中遇到的每一個數。例如對 n3 進行驗證的時候,我們需要計算 3、5、8、4、2、1&…

【遙感物候】Matlab求解一元六次多項式,計算植被生長季始期

一元六次多項式能很好的逼近濾波后的曲線,與二次多項式相比,在擬合植被整個生長季曲線方面有更好的優勢,該方法常用來描述北方溫帶和高緯度地區時序NDVI生長季模式。因此,本文使用一元六次多項式來擬合植被整個生長季曲線,效果很好。那么擬合后,這樣解方程呢求生長季參數…

Android之給控件添加水紋波效果

1 問題 給控件添加水紋波效果,點擊起來像點中了,不然效果太粗糙了,沒反應。 2 實現 給控件添加如下屬性 android:background"?android:attr/selectableItemBackground"波紋有邊界 android:background"?android:attr/sele…

《看聊天記錄都學不會C語言?太菜了吧》(1)我在大佬群里問基礎問題沒人理?

若是大一學子或者是真心想學習的小伙伴可以私聊我,若你是真心學習可以送你書籍,指導你學習,給予你目標方向的學習路線,無套路,博客為證。 本系列文章將會以通俗易懂的對話方式進行教學,對話中將涵蓋了新手…

個人博客作業_week2

1. 是否需要有代碼規范 1.這些規范都是官僚制度下產生的浪費大家的編程時間、影響人們開發效率,浪費時間的東西。 我不同意這個論點。 有句俗語’無規矩不成方圓‘,這亙古傳承的至理同樣適用于寫代碼。制定代碼撰寫規范并不是 迫于壓力完成上級的任務&am…

最全面透徹的RabbitMQ指南

概念RabbitMQ是實現了高級消息隊列協議(AMQP)的開源消息代理軟件(亦稱面向消息的中間件)。RabbitMQ服務器是用Erlang語言編寫的,而集群和故障轉移是構建在開放電信平臺框架上的。所有主要的編程語言均有與代理接口通訊…

TensorFlow中RNN實現的正確打開方式

上周寫的文章《完全圖解RNN、RNN變體、Seq2Seq、Attention機制》介紹了一下RNN的幾種結構,今天就來聊一聊如何在TensorFlow中實現這些結構,這篇文章的主要內容為: 一個完整的、循序漸進的學習TensorFlow中RNN實現的方法。這個學習路徑的曲線較…

【遙感物候】Hants NDVI時間序列諧波分析法數據重構,植被生長季曲線效果可佳(附Hants軟件下載)

NDVI時間序列諧波分析法(Harmonic Analysis of NDVI Time-Series)(簡稱Hants )對時間序列數據進行平滑。該方法是一種新的物候分析方法,可用于定量化的監測植被動態變化。其核心算法是傅里葉變換和最小二乘法擬合, 即把時間波譜數據分解成許多不同頻率的正弦曲線和余弦曲線,…