C/C++?單元自動化測試解決方案總結
前言
C/C++ 開發(fā)效率一直被業(yè)內開發(fā)人員詬病,單元測試開發(fā)效率也是如此,以至于開發(fā)人員不愿花時間來寫單元測試。那么我們是不是可以通過改善編寫單元測試的效率來提升項目的測試用例覆蓋率?
本文主要介紹如何利用GCC插件來實現(xiàn)提升C/C++開發(fā)者的單元效率工具解決方案,希望對大家在提升單元測試效率上有所啟發(fā)。
一、動機
上圖展示了C/C++單元測試的基本流程,在日常開發(fā)過程中寫單元測試是一項比較大工程量的事情,C/C++ 目前單元測試代碼都需要自己手動寫,而且對于一些私有方法打樁就更加麻煩。
目前業(yè)內無開源的自動化測試框架或者工具,倒是有一些商業(yè)的自動測試工具,下圖展示了我們自動化測試工具及單元測試庫:
即使開源界有gtest等測試庫的支持,我們仍然需要編寫大量的單元測試用例代碼。對于一些private、protected的類方法,編寫單元測試用例的效率就更低,需要手動打樁(mock)。同時我們分析測試用例發(fā)現(xiàn),存在很多邊界的用例,它們基本上都是很固定或者有一定模式,比如int 最大最小值等。
如何改善編寫單元測試的效率,提升C/C++同學開發(fā)效率以及程序質量?我們可以通過提取源文件中的函數(shù)、類等信息,然后生成對應的單元測試用例。自動生成用例時需要依賴函數(shù)的聲明、類的聲明等信息,那么我們應該如何獲取這些信息呢?
例如:如下的函數(shù)定義:
void test(int arg) {}
我們希望能夠從上面的函數(shù)定義中得到函數(shù)的返回值類型、函數(shù)名稱、函數(shù)參數(shù)類型、函數(shù)作用域。通常我們可以通過以下幾種方式得到:
1.1 方法1:使用正則表達式
無奈C/C++ 格式比較復雜能夠雖然能夠使用多種組合來獲取對應的函數(shù)聲明等信息:
void test(int arg){} void test1(template<template<string>> arg,...){} void test2(int(*func)(int ,float,...),template<template<string>> arg2){}
那么就需要寫一系列的正則表達式:
- 提取函數(shù)名稱、參數(shù)名:[z-aA-Z_][0-9]+
- 提取函數(shù)返回值:^[a-zA-Z_]
關鍵詞提取出來了,但是他有一個很大的問題:怎么判斷文件中書寫的代碼是符合C/C++語法描述呢?
1.2 方法2:使用flex/bison 分析c/c++源碼文件
這當然是一種很好的方式,但是工作量巨大,相當于實現(xiàn)一個具備詞法、語法分析器簡易版本的編譯器,而且要適配不同的語法格式,雖然bison可以解決上述的如何判斷語法是否正確問題,但是仍然很復雜。
1.3 方法3:利用編譯已經(jīng)生成的AST 來生成代碼
通常我們了解到的GCC編譯的過程是以下四個階段:
源文件->預處理->編譯->匯編→鏈接
但實際上GCC為了支持更多的編程語言、不同的CPU架構做了很多的優(yōu)化,如下圖所示:
上圖展示了GCC處理源碼及其他優(yōu)化過程,在前端部分生成的Generic 語言是gcc編譯過程中為源碼生成的一種與源碼語言無關的抽象語法表現(xiàn)形式(AST)。既然GCC編譯過程中生成了AST樹,那么我們可以通過GCC插件來提取GCC 前端生成的抽象語法樹關鍵信息比如函數(shù)返回值、函數(shù)名稱、參數(shù)類型等??傮w難度也很高,一方面業(yè)內可參考資料很少,只能通過分析GCC的源碼來分析AST語法樹上的各個節(jié)點描述。
本文所描述的自動化生成單元測試用例的解決方案(我們稱之為TU:Translate Unit,后文統(tǒng)稱為TU)就是基于方法3來實現(xiàn)的,下面我們先來看看我們的自動化測試用例解決方案的效果展示。
二、效果展示
2.1 業(yè)務代碼零修改, 直接使用TU生成邊界用例
在該用例中我們不需要修改任何業(yè)務代碼就能夠為業(yè)務代碼生成邊界測試用例,而且函數(shù)參數(shù)可邊界值實現(xiàn)全排列,大大降低用例遺漏風險。大家可能發(fā)現(xiàn)這種沒有做任何修改生成的用例是沒有斷言的,雖然沒有斷言,它仍然能夠幫助發(fā)現(xiàn)單元是否會存在邊界值引起coredump。
那么如果想要給他加上斷言、mock函數(shù),是否沒有辦法呢?通過C++11 [[]] 新的屬性語法,只需要在方法聲明或者定義時添加下根據(jù)TU的格式添加斷言即可,對業(yè)務邏輯無侵入。
2.2 使用注解tu::case生成用戶自定義用例
很多情況下默認生成的邊界測試用例還不能覆蓋到核心邏輯,所以我們也提供tu::case 來給用戶自定義自己的測試用例及斷言。比如有一個int foo (int x,long y) 方法,現(xiàn)在想新增一個測試用例返回值123,函數(shù)實參1,1000,那么只要在函數(shù)聲明前加入,以下代碼即可:
[[tu::case("NE","123","1","1000")]]
2.3 使用注解tu::mock 自動生成mock方法
開發(fā)過程中我們也常需要對某個方法進行mock(即對原有方法設置一個臨時代替方法并且調用方式保持一致),比如某個函數(shù)訪問Redis、DB這種情況下進行單元測試往往需要對這些方法進行mock,方便其他函數(shù)調用進行單元測試,為了方便進行單元測試我們往往會對其進行mock,所以為了方便開發(fā)人員進行快速的mock,所以我們提供了tu::mock 的注解幫助開發(fā)同學快速的定義注解,然后TU會自動生成對應的mock函數(shù)。例如:現(xiàn)在給foo_read 方法mock一個函數(shù),讓mock的函數(shù)返回10:
三、TU實現(xiàn)方案
3.1 AST 是什么?
GENERIC、GIMPLE和RTL三者構成了gcc中間語言的全部,它們以GIMPLE為核心,由GENERIC承上,由RTL啟下,在源文件和目標指令之間的鴻溝之上構建了一個三層的過渡。
GCC在語法分析過程中,所有識別出來的語言部件都用一個叫TREE的變量保存著。這個TREE就是GCC語法樹(AST),這個過程叫做GENERIC。實際上它也是GCC的符號表,因為變量名、類型等等這些信息都由TREE關聯(lián)起來。
下面我們通過gcc編譯選項來看下gcc的ast表現(xiàn)形式:
3.2 AST(Abstract syntax tree)
GCC 可以通過添加編譯選項-fdump-tree-all 來生成ast 樹,ast樹文件內容如下:
AST 各個類型描述可以參考:gcc.gnu.org/onlinedocs/…
雖然上圖中簡單看下一下可以發(fā)現(xiàn),gcc這種表現(xiàn)形式節(jié)點與節(jié)點之間還存在依賴,比較難于理解,沒有clang生成的直觀更容易閱讀。雖然不利于閱讀,但是不影響通過編碼來提取AST信息。
3.3 方案
如上圖所示,我們通過使用不同的插件收集被測試源文件的AST信息、頭文件信息、函數(shù)注解(屬性),將這些重要信息保存起來。
GCC將用戶注冊插件事件保存到數(shù)組中:
然后在編譯構建過程中到就會去查找對應的事件有沒有設置回調方法如果設置則進行調用,TU主要使用以下幾種插件:
- PLUGIN_INCLUDE_FILE 用于獲取當前文件的所包含的頭文件
- PLUGIN_OVERRIDE_GATE 用戶獲取普通函數(shù)、類
- PLUGIN_PRE_GENERICIZE 用于獲取模板函數(shù)的具現(xiàn)化
- PLUGIN_ATTRIBUTES 用于實現(xiàn)自定義屬性或者注解(tu::case\tu::mock ....)
GCC 支持的所有插件類型如下圖所示:(摘自gcc 6.3.0 源碼)
四、TU 插件使用的簡易程度對比
如果僅僅只是做邊界測試那么僅需要修改構建的腳本比如cmake 添加對應的插件參數(shù)即可。
五、使用TU的優(yōu)點
- 接入簡單、邊界單元測試可以做到業(yè)務代碼0修改
- 函數(shù)參數(shù)可邊界值實現(xiàn)全排列,大大降低用例遺漏風險、減少大量重復性的工作
- 快速生成用戶自定義用例、mock方法等
六、TU支持的功能
七、總結與展望
1、文章中對比了三種方法自動生成測試用例的方法,下面對這幾種方法進行對比:
2、文章中還主要介紹了TU的功能特點以及基于GCC-AST的實現(xiàn)自動生成測試用例的解決方案。
TU解決方案目前在構建時能夠自動生成測試用例已經(jīng)極大降低了單元測試門檻提升單元測試覆蓋率,未來我們也希望能夠把TU與IDE相結合,探索更高效便捷的使用方式,通過更加便捷的方式生成指定方法的測試用例。比如通過在函數(shù)、方法上,通過快捷鍵生成當前方法的測試用例等。
到此這篇關于C/C++ 單元自動化測試解決方案總結的文章就介紹到這了,更多相關 C++ 單元測試內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++深入刨析優(yōu)先級隊列priority_queue的使用
最近我學習了C++中的STL庫中的優(yōu)先級隊列(priority_queue)容器適配器,對于優(yōu)先級隊列,我們不僅要會使用常用的函數(shù)接口,我們還有明白這些接口在其底層是如何實現(xiàn)的2022-08-08C++成員函數(shù)如何當作回調函數(shù)同時傳遞this指針
這篇文章主要介紹了C++成員函數(shù)如何當作回調函數(shù)同時傳遞this指針,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-11-11C++實現(xiàn)LeetCode(81.在旋轉有序數(shù)組中搜索之二)
這篇文章主要介紹了C++實現(xiàn)LeetCode(81.在旋轉有序數(shù)組中搜索之二),本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下2021-07-07詳解C語言中free()函數(shù)與getpagesize()函數(shù)的使用
這篇文章主要介紹了詳解C語言中free()函數(shù)與getpagesize()函數(shù)的使用,是C語言入門學習中的基礎知識,需要的朋友可以參考下2015-08-08Java?C++?算法題解leetcode669修剪二叉搜索樹示例
這篇文章主要為大家介紹了Java?C++?算法題解leetcode669修剪二叉搜索樹示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09