前言
單元測試是保證軟件質量很是有效的手段,不管是從測試理論早期介入測試的理念來看或是從單元測試不受UI影響能夠高速批量驗證的特性,因此業界所倡導的測試驅動開發,這個裏面提到的測試驅動更多的就是指單元測試驅動。但通常開發團隊仍是不多的系統化的執行單元測試,針對應用軟件的測試更可能是由專業測試團隊來執行黑盒測試。單元測試的最大的難點不在於沒法肯定輸入輸出,這畢竟是模塊開發階段就已經定好的,而在於單元測試用例的編寫會耗費開發人員大量的工時,按照相關統計單元測試用例的時間甚至會遠超過功能自己開發的時間。如下是幾個最多見的開發不寫單元測試的理由:
●需求老是無窮盡的,還有下階段功能需求要實現,沒空補單元
●要補的單元測試太多,無從下手,主觀上抗拒。
●單元測試編寫難度大。一方面緣由多是功能函數實現上不夠合理,另外一方面是沒有(或者不知道)好用的單元測試框架和mock框架。
●單元測試不算入工做量內。
其次,功能需求還不穩定,寫單元測試的性價比不高。換句話說,萬一明天需求一變,那不光功能代碼廢了,單元測試也廢了。若是不寫單元測試,那這部分工夫就不會白費。
上述幾點其實分析根本緣由是單元測試編寫太耗時,最終致使測試驅動的發動機失去了動力,導致測試驅動開發的美好願景在現實場景熄火,由於構建這個驅動用的發動機實在是難度和成本太大了。 市場上的各類「x」Unit,單元測試框架僅僅解決了生成測試驅動的外框,沒有任何基於深度程序理解的用例邏輯和數據的產生能力。所以在各類開發相關場景中都讓開發人員產生抵觸情緒。Wings的發佈(目前針對C語言)則解決了這個困擾程序員的一個最大的難題,同時也有可能從根本上改變單元測試的現狀,充分的、高效率的單元測試將有效緩解基於海量人力的系統級黑盒測試以及自動化測試的壓力。
制約測試用例採用程序自動生成,最關鍵的底層技術是複雜的參數解析技術。即:可以在編譯器層面對於任意複雜的類型,任意定義嵌套層級的遞歸解析。若是沒有這個關鍵技術的突破,那麼測試用例自動生成系統要麼沒法商用,要麼將以極低的效率來演化、產生合規的測試數據。例如著名的模糊測試工具American Fuzzy Lop,它並不可以識別用戶的程序所須要的結構類型,須要從最外層進行基於搜索算法的演化。程序的特性是接口層面的輸入和內部某個模塊的數據要求距離很遠,外部數據一般是通過層層複雜轉換才能夠成爲內部模塊所須要的數據結構類型,所以從外層探索所須要的計算量和時間將是不可思議的。基於American Fuzzy Lop,爲了可以生成一個合法的SQL 語句,讓程序內部模塊可以經過外圍數據校驗須要探索時間以天數計,遠非分鐘或者小時能夠生成。另一個制約性條件是:每一個程序可以接手的輸入都是通過精心結構編制、含有大量規則的數據,而這些數據經過隨機+探索的方式生成是很是不現實和極其耗時的。因此,從黑盒以及最外層輸入產生自動產生用例是不可行的。
若是從軟件內部結構分析產生用例驅動,就須要對軟件的編譯結構進行深度理解。可行的測試用例生成系統,應該是基於程序的中間(關鍵入口)做爲測試切入最爲合適。這些模塊的輸入,已經將模糊的輸入轉化爲高度結構化的參數。只要可以識別這些複雜結構,將複雜數據類型一步步降解爲簡單數據類型,同時完成參數構造,就能夠自動完成驅動用例的生成。
基於模塊的測試,能夠劃歸爲傳統的單元測試,它是將缺陷發現並遏制在研發階段最好的方法。但受限於單元測試須要開發大量的驅動程序,在行業內的推廣和應用受到了極大的限制。固然單元測試也能夠在系統集成完畢後執行,避免構建虛擬的樁程序。
星雲測試日前全球首發的Wings產品,是一個智能的、全自動的單元測試用例生成系統,研究並解決了以下難點,現分享給你們。
(1) 程序參數深度分析問題
Wings經過編譯器底層技術,將輸入的源文件,按照函數爲單位,造成模塊對象。對象中包含函數的輸入參數,返回值類型等信息,供驅動函數模塊和測試用例模塊使用。每一個文件做爲一個單元,針對其中的每一個函數的每一個參數進行深度解析,對於嵌套類型,複雜類型等均可以實現精確的解析和分解,將複雜類型逐層講解爲基礎數據類型,併產生參數結構的描述文件(PSD)。
(2) 函數驅動自動生成模塊
依據PSD文件的格式信息,自動生成被測源程序的全部驅動函數,單元測試過程再也不依賴開發人員手動編寫測試函數,只需將生成的驅動函數和被測源文件一塊兒編譯,便可執行測試並查看測試結果。測試驅動自動生成程序基於PSD描述,全自動構建驅動被測程序運行的全部參數,必須的全局變量,並可根據複雜變量的層級結構產生結構化的測試驅動程序,能夠節省大量的單元測試用例的編寫時間。
(3) 測試數據自動生成與管理
用於自動生成測試數據,測試數據與被測函數提取的信息相互對應,數據以必定的層次邏輯關係存儲在json文件中。數據和通過分解和展開後的數據類型是一一對應的。這些數據用戶能夠根據業務要求隨意邊際,而且用json文件進行結構化,層次化展現,很是的清晰。其中的測試數據包括全局變量值、被測函數調用時的參數值。
Wings提供了一種自動生成驅動函數的單元測試方法,其中主要包含如下幾個步驟:
c++
圖一:單元測試驅動生成流程
1. 被測程序信息提取
經過對源程序的掃描提取出函數的結構信息,使用戶不須要關心程序的結構信息,而被測程序的結構信息,主要包含程序中的全局變量以及函數信息,而函數信息主要包括函數的參數個數,參數類型以及返回值類型。而全局變量以及參數,最主要的提取出其中的符號信息,以及類型信息,針對一些複雜的類型,經過層層進行解析爲基本數據類型,完成全局變量以及函數參數的構造。git
變量的類型通常大體分爲基本類型、構造類型、指針類型及空類型。Wings經過底層編譯技術,針對不一樣的變量類型,進行不一樣的處理方式。
(1)基本類型,例如unsigned int u_int = 20等基本類型,Wings將解析出變量的名稱爲u_int,數據類型爲unsigned int。
(2) 構造類型,構造類型大體分爲數組,結構體,共用體,枚舉類型。
數組類型,例如int array[2][3],數組名稱爲array,類型爲int以及二維數組的長度,行爲2,列爲3。
結構體類型,針對結構體爲數組,結構體鏈表等,進行不一樣的標記劃分。
(3) 指針類型,例如int **ptr = 0;,解析出指針爲int類型的2級指針。
(4) 空類型,解析出類型爲NULL。
(5) 系統類型,例如File、size_t等,標記爲系統類型,不在對其往下進行分析,會添加到模板中,由用戶進行賦值操做。
(6) 函數指針類型,分析出函數的返回值類型、參數類型以及參數個數
針對被測源程序的每一個編譯單元,將解析到的函數信息,保存在對應的PSD結構中,針對如下源代碼實例進行說明:程序員
typedef struct my_structone { //基本類型 int i_int; //數組類型 int array_one[2]; int array_two[3][4]; //指針類型 int *point_one; int **point_two; //空類型 void *point; //位域類型 unsigned int w : 1; //函數指針是指向函數的指針變量,即本質是一個指針變量 int(*functionPtr)(int, int); union { int a; char b; long long c; }Dem; enum DAY { MON = 1, TUE, WED = 200, THU, FRI = 100, SAT, SUN }dy; }myy_structone; typedef struct my_struct { //結構體包含結構體 myy_structone *structone; //結構體中包含系統頭文件的類型 FILE file; struct my_struct *next; }myy_struct; //結構體做爲函數參數 void StructTypeTest1(myy_struct m_struct); void StructTypeTest2(myy_struct *mm_struct); void StructTypeTest3(myy_struct mm_struct[2]); void StructTypeTest4(myy_struct mm_struct[2][3]);
以上程序中,void StructTypeTest3(myy_struct mm_struct[2])保存的PSD結構以下:算法
<StructTypeTest3 parmType0="myy_struct [2]" parmNum="1"> <mm_struct baseType1="ArrayType" RowSize="2" type="StructureOrClassType" name="my_struct"> <structone baseType1="PointerType" type="StructureOrClassType" name="my_structone"> <i_int baseType1="BuiltinType" type="ZOA_INT" /> <array_one baseType1="ArrayType" RowSize="2" type="ZOA_INT" /> <array_two baseType1="ArrayType" RowSize="3" baseType2="ArrayType" ColumnSize="4" type="ZOA_INT" /> <point_one baseType1="PointerType" type="ZOA_INT" /> <point_two baseType1="PointerType" baseType2="PointerType" type="ZOA_INT" /> <point baseType1="PointerType" type="ZOA_VOID" /> <w baseType1="BuiltinType" type="ZOA_UINT" bitfield="1" /> <functionPtr baseType1="FunctionPointType" type="ZOA_FUNC" returnType="int" parmType0="int" parmType1="int" parmNum="2" /> <Dem baseType1="UnionType" type="ZOA_UNION" name="NULL"> <a baseType1="BuiltinType" type="ZOA_INT" /> <b baseType1="BuiltinType" type="ZOA_CHAR_S" /> <c baseType1="BuiltinType" type="ZOA_LONGLONG" /> </Dem> <dy baseType1="EnumType" type="ZOA_ENUM" name="DAY"> <MON type="ZOA_INT" value="1" /> <TUE type="ZOA_INT" value="2" /> <WED type="ZOA_INT" value="200" /> <THU type="ZOA_INT" value="201" /> <FRI type="ZOA_INT" value="100" /> <SAT type="ZOA_INT" value="101" /> <SUN type="ZOA_INT" value="102" /> </dy> </structone> <file baseType1="StructureOrClassType" type="StructureOrClassType" name="_iobuf" SystemVar="_iobuf" /> <next NodeType="LinkNode" baseType1="PointerType" type="StructureOrClassType" name="my_struct" /> </mm_struct> <g_int globalType="globalVar" /> <returnType returnType="void" /> </StructTypeTest3>
其中PSD文件各節點表明的意義以下:
StructTypeTest3表明函數名,parmType0表明參數類型,parmNum表明參數個數
mm_struct表明函數參數的符號,baseType1表明類型的分類(基本數據類型、構造類型、指針類型、空類型),type表明具體的類型,包括int,char,short,long,double,float,bool,以及這些類型的unsigned類型等基礎的類型,還有一些特殊的類型諸如:ZOA_FUN類型表示函數類型,StructureOrClassType表示結構體類型,等等,name表明結構體、聯合體、枚舉類型的名稱
i_int表明基本類型,基本類型做爲最小的賦值單位
array_one表明數組類型,RowSize表明數組的長度,數組能夠劃分爲一維數組,二維數組等
point表明指針類型,指針分爲一級指針、二級指針等,通常指針當作函數參數做爲數組使用,所以,針對基本類型的指針,採用動態分配數組的方式進行賦值,用戶可依據須要,修改對應的值文件。
w表明位域類型,bitfileld表明所佔位數
functionPtr表明函數指針類型,分別分析出參數類型、參數個數、返回值信息
Dem表明聯合體類型
dy表明枚舉類型,value表明枚舉類型的取值
file表明結構體類型,SystemVar表明此變量屬於系統頭文件中的變量,針對此種類型的變量,Wings經過添加模板變量的方式,添加在模板庫中,用戶可依據具體須要進行特殊賦值。例如File類型的,處理方式爲:sql
/* 系統內置類型,特殊處理或者模板處理 */ char * fname = "E:/spacial.txt"; FILE * file = fopen(fname,"r"); _st.file = _file;
用戶也可自行添加賦值方式。針對系統類型,Wings能夠和普通用戶自定義類型進行區分,當解析到系統內置類型的時候就能夠中止向下進行遞歸分析。
g_int表明全局變量,globalType表明全局
next表明鏈表結構體,NodeType表明此結構爲鏈表
returnType表明函數的返回值類型。
2. 驅動程序的自動生成
在上文中,針對全局變量和函數的結構信息,進行了分析和提取,如下將利用提取到保存在PSD中的信息,完成被測源程序的驅動框架總體生成。
生成主要分爲如下幾個方面:
全局變量的聲明
函數參數的賦值操做,針對函數參數的個數,依次賦值操做
全局變量的賦值,針對分析獲得函數使用的全局變量的個數,依次進行賦值操做
原函數的調用
一些須要注意點以下:
驅動生成過程當中,針對一些特殊函數,例如main函數,static函數等,由於外部沒法訪問到,驅動生成暫時不作處理。
針對每一個被測源文件,生成對應的一個驅動文件。
驅動控制包含在Driver_main.cpp中,能夠經過宏自動配置函數的測試次數
由以上源程序,生成的驅動函數以下:
全部變量的命名爲在原變量的名稱前,添加_
經過獲取生成對應的測試數據,對變量依次進行賦值操做
針對系統內置參數,以及用戶比較特殊的參數,經過模板方式統一配置賦值方式。
* 對被測函數進行參數賦值與調用。
3 測試數據自動生成
測試用例的自動生成,利用提取到保存在PSD中的函數信息,進行測試用例數據的生成,如下是圖三中PSD格式生成的一組數據,每組數據保存爲JSON格式,更容易看到數據的層次關係。json
"StructTypeTest30" : { "g_int" : 11624, "mm_struct" : [ { "file" : "NULL", "next" : "NULL", "structone" : { "Dem" : { "a" : 20888, "b" : "A", "c" : 19456 }, "array_one" : [ 24441, 12872 ], "array_two" : [ [ 18675, 30300, 32216, 19566 ], [ 13566, 13319, 11179, 18867 ], [ 30514, 21664, 21641, 28262 ] ], "dy" : 101, "functionPtr" : "NULL", "i_int" : 18271, "point_one" : [ 28024, 32245, 2129 ], "point_two" : [ [ 18165, 32335, 6429 ], [ 30225, 18252, 2764 ], [ 3177, 3622, 29789 ] ], "w" : 16862 } }, { "file" : "NULL", "next" : "NULL", "structone" : { "Dem" : { "a" : 2651, "b" : "7", "c" : 12159 }, "array_one" : [ 1274, 24318 ], "array_two" : [ [ 27944, 1208, 29647, 20840 ], [ 4972, 27297, 17456, 13614 ], [ 22441, 1160, 8940, 29420 ] ], "dy" : 200, "functionPtr" : "NULL", "i_int" : 15434, "point_one" : [ 29394, 3868, 25406 ], "point_two" : [ [ 13575, 14736, 20728 ], [ 9132, 2297, 2113 ], [ 26252, 14896, 10985 ] ], "w" : 12354
針對每一個編譯單元,默認生成一組全部函數的對應的測試數據文件,值生成能夠經過配置次數進行修改。
4 Mysql程序測試結果展現
如何完成驅動框架的生成,下面針對開源程序MySQL完整的生成過程,進行詳細說明。
如下是Wings測試Mysql的主界面圖:
點擊文件按鈕,設置被測源程序的工程目錄。設置完成以後,點擊功能操做,功能操做主要包括參數解析、驅動生成、值文件生成以及模板添加四個操做。分析對應生成如下幾個文件夾:
其中,參數解析模塊,對應生成FunXml以及GlobalXml,分別存放提取到的每一個編譯單元的函數信息及全局變量的信息。
驅動生成模塊,會對應生成Wings_Projects文件夾,其中存放每一個編譯單元的驅動文件
值生成模塊,存放每一個編譯單元的生成的測試數據。
下圖爲Mysql對應加載的驅動文件結構體信息,左側導航樹爲生成的對應驅動文件,包含每一個編譯單元的函數以及函數的參數、全局變量的信息。點擊其中某個編譯單元,能夠加載對應的驅動文件以及對應的值文件。
數組
以上是Mysql的總體生成對應的驅動文件以及值文件,針對如下代碼詳細說明驅動文件。
針對每一個編譯單元,全局變量的引用經過extern的方式。
驅動函數,統一命名爲Driver_XXX的方式,JSON做爲獲取測試數據的方式,times表明單函數的測試次數。
* 針對每一個參數的賦值操做,利用解析到的PSD存儲格式,對每層結構依次進行賦值操做。
Wings的應用很是簡單,下面是以在Visual Studio 2015中可正常編譯的Mysql 代碼爲例,生成的測試數據的統計指標,整個生成過程無需任何人工介入,僅須要制定所須要生成驅動的源碼的路徑便可。
數據結構
如下是使用源碼統計工具獲得的結果,多達400多萬行有效的單元測試代碼是由Wings全自動生成的。更有意思的是:能夠看到這些代碼採用人工開發的成本高達1079我的月,成本更是達到了1079萬之多。框架
Wings實現了由程序自動生成程序的第一步探索,目前發佈的是初版,有興趣的開發者直接在碼雲平臺(https://gitee.com/teststars/wings_release進行下載),商業受權提供了一個月無限功能體驗期,能夠快速體驗Wings的神奇能力,Wings c語言版支持多平臺,例如visual studio、vxworks、gcc、qt等。Wings由星雲測試(www.teststar.cc)團隊設計和研發,有興趣的開發者能夠經過碼雲的互動平臺與星雲測試團隊取得聯繫,貢獻本身的設計思路和產品使用反饋(凡被採納的優秀建議,星雲能夠延長其無償使用期至少爲三個月)。Wings具備強大的、底層的大幅度改進軟件質量的基因,將來Wings的將深度優化自動編寫的程序的可讀性(更接近優秀程序員的編寫水平)以及對於c++語言的支持。 ide