【星雲測試】Wings-讓單元測試智能全自動生成

Wings-讓單元測試智能全自動生成

前言
  單元測試是保證軟件質量非常有效的手段,無論是從測試理論早期介入測試的理念來看或是從單元測試不受UI影響可以高速批量驗證的特性,所以業界所倡導的測試驅動開發,這個裏面提到的測試驅動更多的就是指單元測試驅動。但一般開發團隊還是很少的系統化的執行單元測試,針對應用軟件的測試更多是由專業測試團隊來執行黑盒測試。單元測試的最大的難點不在於無法確定輸入輸出,這畢竟是模塊開發階段就已經定好的,而在於單元測試用例的編寫會耗費開發人員大量的工時,按照相關統計單元測試用例的時間甚至會遠超過功能本身開發的時間。以下是幾個最常見的開發不寫單元測試的理由:
 ●需求總是無窮盡的,還有下階段功能需求要實現,沒空補單元
 ●要補的單元測試太多,無從下手,主觀上抗拒。
 ●單元測試編寫難度大。一方面原因可能是功能函數實現上不夠合理,另一方面是沒有(或者不知道)好用的單元測試框架和mock框架。
 ●單元測試不算入工作量內。
  其次,功能需求還不穩定,寫單元測試的性價比不高。換句話說,萬一明天需求一變,那不光功能代碼廢了,單元測試也廢了。如果不寫單元測試,那這部分工夫就不會白費。
上述幾點其實分析根本原因是單元測試編寫太耗時,最終導致測試驅動的發動機失去了動力,致使測試驅動開發的美好願景在現實場景熄火,因爲構建這個驅動用的發動機實在是難度和成本太大了。 市場上的各種“x”Unit,單元測試框架僅僅解決了生成測試驅動的外框,沒有任何基於深度程序理解的用例邏輯和數據的產生能力。因此在各種開發相關場景中都讓開發人員產生抵觸情緒。Wings的發佈(目前針對C語言)則解決了這個困擾程序員的一個最大的難題,同時也有可能從根本上改變單元測試的現狀,充分的、高效率的單元測試將有效緩解基於海量人力的系統級黑盒測試以及自動化測試的壓力。
  制約測試用例採用程序自動生成,最關鍵的底層技術是複雜的參數解析技術。即:能夠在編譯器層面對於任意複雜的類型,任意定義嵌套層級的遞歸解析。如果沒有這個關鍵技術的突破,那麼測試用例自動生成系統要麼無法商用,要麼將以極低的效率來演化、產生合規的測試數據。例如著名的模糊測試工具American Fuzzy Lop,它並不能夠識別用戶的程序所需要的結構類型,需要從最外層進行基於搜索算法的演化。程序的特性是接口層面的輸入和內部某個模塊的數據要求距離很遠,外部數據通常是經過層層複雜轉換纔可以成爲內部模塊所需要的數據結構類型,因此從外層探索所需要的計算量和時間將是難以想象的。基於American Fuzzy Lop,爲了能夠生成一個合法的SQL 語句,讓程序內部模塊能夠通過外圍數據校驗需要探索時間以天數計,遠非分鐘或者小時可以生成。另外一個制約性條件是:每個程序能夠接手的輸入都是經過精心結構編制、含有大量規則的數據,而這些數據通過隨機+探索的方式生成是非常不現實和極其耗時的。所以,從黑盒以及最外層輸入產生自動產生用例是不可行的。
如果從軟件內部結構分析產生用例驅動,就需要對軟件的編譯結構進行深度理解。可行的測試用例生成系統,應該是基於程序的中間(關鍵入口)作爲測試切入最爲合適。這些模塊的輸入,已經將模糊的輸入轉化爲高度結構化的參數。只要能夠識別這些複雜結構,將複雜數據類型一步步降解爲簡單數據類型,同時完成參數構造,就可以自動完成驅動用例的生成。
  基於模塊的測試,可以劃歸爲傳統的單元測試,它是將缺陷發現並遏制在研發階段最好的方法。但受限於單元測試需要開發大量的驅動程序,在行業內的推廣和應用受到了極大的限制。當然單元測試也可以在系統集成完畢後執行,避免構建虛擬的樁程序。
  星雲測試日前全球首發的Wings產品,是一個智能的、全自動的單元測試用例生成系統,研究並解決了如下難點,現分享給大家。
 (1) 程序參數深度分析問題
 Wings通過編譯器底層技術,將輸入的源文件,按照函數爲單位,形成模塊對象。對象中包含函數的輸入參數,返回值類型等信息,供驅動函數模塊和測試用例模塊使用。每個文件作爲一個單元,針對其中的每個函數的每個參數進行深度解析,對於嵌套類型,複雜類型等都可以實現精確的解析和分解,將複雜類型逐層講解爲基礎數據類型,併產生參數結構的描述文件(PSD)。
 (2) 函數驅動自動生成模塊
 依據PSD文件的格式信息,自動生成被測源程序的所有驅動函數,單元測試過程不再依賴開發人員手動編寫測試函數,只需將生成的驅動函數和被測源文件一起編譯,即可執行測試並查看測試結果。測試驅動自動生成程序基於PSD描述,全自動構建驅動被測程序運行的所有參數,必須的全局變量,並可根據複雜變量的層級結構產生結構化的測試驅動程序,可以節省大量的單元測試用例的編寫時間。
 (3) 測試數據自動生成與管理
 用於自動生成測試數據,測試數據與被測函數提取的信息相互對應,數據以一定的層次邏輯關係存儲在json文件中。數據和經過分解和展開後的數據類型是一一對應的。這些數據用戶可以根據業務要求隨意邊際,並且用json文件進行結構化,層次化展示,非常的清晰。其中的測試數據包括全局變量值、被測函數調用時的參數值。
 Wings提供了一種自動生成驅動函數的單元測試方法,其中主要包含以下幾個步驟:
        【星雲測試】Wings-讓單元測試智能全自動生成

             圖一:單元測試驅動生成流程
  1. 被測程序信息提取
 通過對源程序的掃描提取出函數的結構信息,使用戶不需要關心程序的結構信息,而被測程序的結構信息,主要包含程序中的全局變量以及函數信息,而函數信息主要包括函數的參數個數,參數類型以及返回值類型。而全局變量以及參數,最主要的提取出其中的符號信息,以及類型信息,針對一些複雜的類型,通過層層進行解析爲基本數據類型,完成全局變量以及函數參數的構造。

 變量的類型一般大致分爲基本類型、構造類型、指針類型及空類型。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類型的,處理方式爲:


/* 系統內置類型,特殊處理或者模板處理 */
  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中,可以通過宏自動配置函數的測試次數
   由以上源程序,生成的驅動函數如下:
【星雲測試】Wings-讓單元測試智能全自動生成
  
所有變量的命名爲在原變量的名稱前,添加_
   通過獲取生成對應的測試數據,對變量依次進行賦值操作
  
針對系統內置參數,以及用戶比較特殊的參數,通過模板方式統一配置賦值方式。
  * 對被測函數進行參數賦值與調用。
 3 測試數據自動生成
  測試用例的自動生成,利用提取到保存在PSD中的函數信息,進行測試用例數據的生成,以下是圖三中PSD格式生成的一組數據,每組數據保存爲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的主界面圖:
【星雲測試】Wings-讓單元測試智能全自動生成
 點擊文件按鈕,設置被測源程序的工程目錄。設置完成之後,點擊功能操作,功能操作主要包括參數解析、驅動生成、值文件生成以及模板添加四個操作。分析對應生成以下幾個文件夾:
【星雲測試】Wings-讓單元測試智能全自動生成
 其中,參數解析模塊,對應生成FunXml以及GlobalXml,分別存放提取到的每個編譯單元的函數信息及全局變量的信息。
 驅動生成模塊,會對應生成Wings_Projects文件夾,其中存放每個編譯單元的驅動文件
 值生成模塊,存放每個編譯單元的生成的測試數據。
 下圖爲Mysql對應加載的驅動文件結構體信息,左側導航樹爲生成的對應驅動文件,包含每個編譯單元的函數以及函數的參數、全局變量的信息。點擊其中某個編譯單元,可以加載對應的驅動文件以及對應的值文件。
【星雲測試】Wings-讓單元測試智能全自動生成

 以上是Mysql的整體生成對應的驅動文件以及值文件,針對以下代碼詳細說明驅動文件。
   針對每個編譯單元,全局變量的引用通過extern的方式。
  
驅動函數,統一命名爲Driver_XXX的方式,JSON作爲獲取測試數據的方式,times代表單函數的測試次數。
  * 針對每個參數的賦值操作,利用解析到的PSD存儲格式,對每層結構依次進行賦值操作。
 Wings的應用非常簡單,下面是以在Visual Studio 2015中可正常編譯的Mysql 代碼爲例,生成的測試數據的統計指標,整個生成過程無需任何人工介入,僅需要制定所需要生成驅動的源碼的路徑即可。
【星雲測試】Wings-讓單元測試智能全自動生成

 以下是使用源碼統計工具得到的結果,多達400多萬行有效的單元測試代碼是由Wings全自動生成的。更有意思的是:可以看到這些代碼採用人工開發的成本高達1079個人月,成本更是達到了1079萬之多。

  • 【星雲測試】Wings-讓單元測試智能全自動生成

 Wings實現了由程序自動生成程序的第一步探索,目前發佈的是第一版,有興趣的開發者直接在碼雲平臺(https://gitee.com/teststars/wings_release進行下載),商業授權提供了一個月無限功能體驗期,可以快速體驗Wings的神奇能力,Wings c語言版支持多平臺,例如visual studio、vxworks、gcc、qt等。Wings由星雲測試(www.teststar.cc)團隊設計和研發,有興趣的開發者可以通過碼雲的互動平臺與星雲測試團隊取得聯繫,貢獻自己的設計思路和產品使用反饋(凡被採納的優秀建議,星雲可以延長其免費使用期至少爲三個月)。Wings具有強大的、底層的大幅度改進軟件質量的基因,未來Wings的將深度優化自動編寫的程序的可讀性(更接近優秀程序員的編寫水平)以及對於c++語言的支持。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章