C開發中的單元測試(Cunit)


C開發中的單元測試

最近在寫C代碼的過程中,感覺自己在重復一項必不可少的環節,就是自測代碼,這使我想起以前在寫JAVA時的Junit帶來的快捷和方便,於是馬上行動,經百度、谷歌幾輪后,發現Cunit工具,看名字,就可猜到它與Junit同屬一宗。網上的相關內容也都非常雷同,這里不再詳述,有興趣的話,可以直奔官方文檔:http://cunit.sourceforge.net/doc/index.html

經過仔細觀賞,要借用Cunit來提高自己的編碼效率和質量,有必要先搞清它的幾項要點:
首先仔細觀察下圖:

可以看出Cunit也是有組織的,呵呵,主要分幾個角色,Registry,Suite及Test方法。可以通過下面例子,體會到這種組織關系。
按官方文檔說明,使用Cunit的主要步驟有:
1) Write functions for tests (and suite init/cleanup if necessary).
2) Initialize the test registry - CU_initialize_registry()
3) Add suites to the test registry - CU_add_suite()
4) Add tests to the suites - CU_add_test()
5) Run tests using an appropriate interface, e.g. CU_console_run_tests
6) Cleanup the test registry - CU_cleanup_registry

本人英文不咋地,就不獻丑翻譯了,直接用英文理解吧(要努力用英文).
下面我們結合一個小實例,來體驗一下Cunit的便利吧(學編程,最有效的方式還是自己動手)
先編寫一個具體兩個簡單功能的函數,然后寫Testcase來測試它。
文件主要有:
1) strformat.h :字符串功能函數的接口文件
2)strformat.c :字符串功能函數的實現
3)testcase.c : 測試用例及Cunit運行環境
4)makefile :
下面直奔代碼:
代碼:strformat.h

 1 /* strformat.h --- 
 2  * 
 3  * Filename: strformat.h
 4  * Description: 字符串操作頭文件
 5  * Author: magc
 6  * Maintainer: 
 7  * Created: 一  8月 20 22:57:19 2012 (+0800)
 8  * Version: 
 9  * Last-Updated: 六  8月 25 10:31:30 2012 (+0800)
10  *           By: magc
11  *     Update #: 15
12  * URL: 
13  * Keywords: 
14  * Compatibility: 
15  * 
16  */
17 
18 /* Commentary: 
19  * 為的是體驗Cunit而臨時寫的幾項功能函數,沒有多大實際意義,僅僅是為了寫測試類
20  * 
21  * 
22  */
23 
24 /* Change Log:
25  * 
26  * 
27  */
28 
29 /* Code: */
30 
31 #ifndef _strformat_h
32 #define _strformat_h
33 
34 typedef char * string;
35 
36 /*************************************************************************
37 *功能描述:返回字符串的長度
38 *參數列表:
39 *返回類型:
40 **************************************************************************/    
41 int string_lenth(string word);
42 /*************************************************************************
43 *功能描述:返回字符串的大寫形式
44 *參數列表:
45 *返回類型:
46 **************************************************************************/
47 string to_Upper(string word);
48 /*************************************************************************
49 *功能描述:連接字符串
50 *參數列表:
51 *返回類型:
52 **************************************************************************/
53 string add_str(string word1 ,string word2);
54 
55 
56 
57 #endif
58 
59 
60 /* strformat.h ends here */

 

代碼:strformat.c

 1 /* strformat.c --- 
 2  * 
 3  * Filename: strformat.c
 4  * Description: 字符串操作
 5  * Author: magc
 6  * Maintainer: 
 7  * Created: 一  8月 20 22:56:36 2012 (+0800)
 8  * Version: 
 9  * Last-Updated: 六  8月 25 10:33:07 2012 (+0800)
10  *           By: magc
11  *     Update #: 33
12  * URL: 
13  * Keywords: 
14  * Compatibility: 
15  * 
16  */
17 
18 /* Commentary: 
19  * 此代碼僅為體驗Cunit而臨時撰寫。
20  * 
21  * 
22  */
23 
24 /* Change Log:
25  * 
26  * 
27  */
28 
29 /* Code: */
30 #include <assert.h>
31 #include <ctype.h>
32 #include <errno.h>
33 #include <limits.h>
34 #include <string.h>
35 #include <stdarg.h>
36 #include <stdlib.h>
37 #include <stdio.h>
38 #include "strformat.h"
39 
40 
41 /**************************************************************************
42 函數名稱:字符串相加
43 功能描述:
44 輸入參數:
45 返   回:
46 **************************************************************************/
47 string add_str(string word1 ,string word2){
48     return (strcat(word1, word2));
49 }
50 
51 /**************************************************************************
52 函數名稱:將字符串轉換成大寫格式
53 功能描述:
54 輸入參數:
55 返   回:
56 **************************************************************************/
57 string to_Upper(string word){
58     int i;
59     for(i = 0;word[i] !='\0' ;i++){
60         if(word[i]<'z' && word[i]>'a'){
61             word[i] -= 32;
62         }
63     }
64     return word;
65     
66 }
67 
68 /**************************************************************************
69 函數名稱:字符串長度
70 功能描述:
71 輸入參數:
72 返   回:
73 **************************************************************************/
74 int string_lenth(string word){
75     int i;
76     for(i = 0 ;word[i] != '\0';i++){
77         
78     }
79     return i;
80 }
81 
82 /* strformat.c ends here */

 

測試代碼: testcase.c

  1 /* testcase.c --- 
  2  * 
  3  * Filename: testcase.c
  4  * Description: 測試實例 
  5  * Author: magc
  6  * Maintainer: 
  7  * Created: 一  8月 20 23:08:53 2012 (+0800)
  8  * Version: 
  9  * Last-Updated: 五  8月 24 16:09:40 2012 (+0800)
 10  *           By: magc
 11  *     Update #: 135
 12  * URL: 
 13  * Keywords: 
 14  * Compatibility: 
 15  * 
 16  */
 17 
 18 /* Commentary:
 19  * 當前文件用來定義測試方法,suite,及registry信息,若測試方法有變化,只需要修改當前文件即可。
 20  * 第一步:書寫測試函數的代碼,建議以"test_"為前綴。
 21  * 第二步:將測試方法歸類,即將相似功能的測試方法放到一個數組里,以便把它們指定給一個suite
 22  * 第三步:創建suite,可按功能或模塊,生成多個test suite,
 23  * 第四步:書寫測試方法的總調用方法,AddTests(),用來統一啟動測試方法。
 24  */
 25 
 26 /* Change Log:
 27  * 
 28  * 
 29  */
 30 
 31 /* Code: */
 32 #include <assert.h>
 33 #include <ctype.h>
 34 #include <errno.h>
 35 #include <limits.h>
 36 #include <string.h>
 37 #include <stdarg.h>
 38 #include <stdlib.h>
 39 #include <stdio.h>
 40 
 41 #include <CUnit/Basic.h>
 42 #include <CUnit/Console.h>
 43 #include <CUnit/CUnit.h>
 44 #include <CUnit/TestDB.h>
 45 #include "strformat.h"
 46 
 47 /**************************************************************************
 48 函數名稱:測試string_lenth()方法
 49 功能描述:
 50 輸入參數:
 51 返   回:
 52 **************************************************************************/
 53 void test_string_lenth(void){
 54     string test = "Hello";
 55     int len = string_lenth(test);
 56     CU_ASSERT_EQUAL(len,5);
 57 }
 58 
 59 /**************************************************************************
 60 函數名稱:測試方法to_Upper()
 61 功能描述:
 62 輸入參數:
 63 返   回:
 64 **************************************************************************/
 65 
 66 void test_to_Upper(void){
 67     char test[] = "Hello";
 68     CU_ASSERT_STRING_EQUAL(to_Upper(test),"HELLO");
 69     
 70 }
 71 
 72 /**************************************************************************
 73 函數名稱:測試方法 add_str()
 74 功能描述:
 75 輸入參數:
 76 返   回:
 77 **************************************************************************/
 78 void test_add_str(void){
 79     char test1[] = "Hello!";
 80     char test2[] = "MGC";
 81     CU_ASSERT_STRING_EQUAL(add_str(test1,test2),"Hello!MGC");
 82     
 83 }
 84 
 85 /**************************************************************************
 86 數組名稱:將多個測試方法打包成組,以供指定給一個Suite
 87 功能描述:每個suite可以有一個或多個測試方法,以CU_TestInfo數組形式指定
 88 **************************************************************************/
 89 // CU_TestInfo是Cunit內置的一個結構體,它包括測試方法及描述信息
 90 CU_TestInfo testcase[] = {
 91     {"test_for_lenth:",test_string_lenth    },
 92     {"test_for_add:",test_add_str    },
 93     CU_TEST_INFO_NULL
 94 };
 95 
 96 CU_TestInfo testcase2[] = {
 97     {"test for Upper :",test_to_Upper    },
 98     CU_TEST_INFO_NULL
 99 };
100 
101 /**************************************************************************
102 函數名稱:suite初始化過程
103 功能描述:
104 輸入參數:
105 返   回:
106 **************************************************************************/
107 int suite_success_init(void){
108     return 0;
109     
110 }
111 
112 /**************************************************************************
113 函數名稱:suite清理過程,以便恢復原狀,使結果不影響到下次運行
114 功能描述:
115 輸入參數:
116 返   回:
117 **************************************************************************/
118 int suite_success_clean(void){
119     return 0;
120 }
121 
122 //定義suite數組,包括多個suite,每個suite又會包括若干個測試方法。
123 CU_SuiteInfo suites[] = {
124     {"testSuite1",suite_success_init,suite_success_clean,testcase},
125     {"testsuite2",suite_success_init,suite_success_clean,testcase2},
126     CU_SUITE_INFO_NULL
127 };
128 
129 /**************************************************************************
130 函數名稱:測試類的調用總接口
131 功能描述:
132 輸入參數:
133 返   回:
134 **************************************************************************/
135 void AddTests(){
136     assert(NULL != CU_get_registry());
137     assert(!CU_is_test_running());
138 
139     if(CUE_SUCCESS != CU_register_suites(suites)){
140         exit(EXIT_FAILURE);
141 
142     }
143 }
144 /*************************************************************************
145 *功能描述:運行測試入口
146 *參數列表:
147 *返回類型:
148 **************************************************************************/
149 
150 int RunTest(){
151         if(CU_initialize_registry()){
152                 fprintf(stderr, " Initialization of Test Registry failed. ");
153                 exit(EXIT_FAILURE);
154         }else{
155                 AddTests();
156                 /**** Automated Mode *****************
157                 CU_set_output_filename("TestMax");
158                 CU_list_tests_to_file();
159                 CU_automated_run_tests();
160                 //************************************/
161                 
162                 /***** Basice Mode *******************
163                 CU_basic_set_mode(CU_BRM_VERBOSE);
164                 CU_basic_run_tests();
165                 //************************************/
166 
167                 /*****Console Mode ********************
168                 CU_console_run_tests();
169                 //************************************/
170 
171                 CU_cleanup_registry();
172                 
173                 return CU_get_error();
174                 
175         }
176 
177 }
178 /*************************************************************************
179 *功能描述:測試類主方法
180 *參數列表:
181 *返回類型:
182 **************************************************************************/
183 
184 int main(int argc, char * argv[])
185 {
186    return  RunTest();
187     
188 }
189 
190 
191 
192 
193 
194 /* testcase.c ends here */

注:
1)注意結合上面Cunit的組織結構圖,理解Cunit中幾個角色的關系(CU_TestInfo,CU_SuiteInfo各以數組的形式,將多個Test和Suite組織起來)。
2)Cunit有幾種運行模式,如automated,basic,console,有的是可以交互的,有的是沒有交互,直接出結果的。

 

代碼:makefile

IINC=-I/usr/local/include/CUnit
LIB=-L/usr/local/lib/

all:  strformat.c testcase.c
    gcc -o test $(INC) $(LIB)  $^ -lcunit -static

注:
1)Cunit安裝很簡單,從官方地址上下載源代碼后,在本機依次執行
./configure
make 
sudo make install
安裝成功后相關的庫及頭文件安裝到默認路徑下。編譯時添加選項:
-I/usr/local/include/CUnit
-L/usr/local/lib/
就如makefile中的一樣。

 


下面我們欣賞一下Cunit的常見幾種運行模式
1)Automated Mode
先將testcase.c中156~159代碼放開注釋,此時便是以automated模式運行,此模塊沒有交互能力,直接生成XML格式的報表,先make,然后運行后,在當前目錄下生成兩個報表
TestMax-Listing.xml和TestMax-Results.xml(前者是測試用例的列表,后者是測試用例的測試結果) ,但這兩個文件是不能直接觀看的,要查看這兩個文件,需要使用如下xsl和dtd文件:CUnit-List.dtd和CUnit-List.xsl用於解析列表文件, CUnit-Run.dtd和CUnit-Run.xsl用於解析結果文件。這四個文件在CUnit包里面有提供,安裝之后在$(PREFIX) /share/CUnit目錄下,默認安裝的話在/home/lirui/local/share/CUnit目錄下。在查看結果之前,需要把這六 個文件:TestMax-Listing.xml, TestMax-Results.xml, CUnit-List.dtd, CUnit-List.xsl, CUnit-Run.dtd, CUnit-Run.xsl拷貝到一個目錄下,然后用瀏覽器打開兩個結果的xml文件就可以了。
如下圖所示:

 

 

2) Console Mode
在testcase.c中將其它模式代碼注釋掉,放開168行代碼,便轉換成Console模式了。console模式支持交互,如支持運行,查看錯誤等操作,如下圖所示:

從上圖即可看出,輸入R來運行,輸入F來查看錯誤用例,輸入Q來退出

這種模式在實際中是很實用的。

3)Basic Mode
在testcase.c中將其它模式的代碼注釋掉,放到163~164行代碼,便轉換成Basic模式,這種是不支持交互的,直接打印出運行結果。

可以看出,這種模式也是比較簡單快捷的

 


另外對於這種寫測試代碼的重復工作,可以想辦法減小重復,還好,我用的是Emacs寫代碼,可以借助強大的msf-abbrev 來定義一個testcase的模板,每次寫測試時,可以直接引入,既簡單又快捷,使自己將有限的精力集中到更核心的部分。(假如你不知我在說什么,就當沒看到這部分,直接閃過)

 


小結:
以后寫代碼過程中,若需要測試函數的功能,就可以采用如下步驟:
1)創建一個專門的測試類,用msf-abbrev模板快捷創建內容,
2) 添加測試函數
3) 將測試函數按組放到CU_TestInfo數組中,並指定給一個Suite
4)根據自己需要,定義CU_SuiteInfo數組。
5)在RunTest()中定義運行模式。
6)在main函數中調用RunTest(),默認生成的testcase中有一個main函數,若不與其它沖突,直接在這里調用即可。(若main沖突,則砍掉不需要的那個)

 

代碼附件


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM