C語言單元測試

Just4life發表於2013-08-26

C語言單元測試

 對於敏捷開發來說,單元測試必不可少,對於Java開發來說,JUnit非常好,對於C++開發,也有CPPUnit可供使用,而對於傳統的C語言開發,就沒有很好的工具可供使用,可以找到的有這麼幾個工具:

  1.  CuTest -- CuTest(Cute Test)是一個非常簡單的C語言單元測試工具。在使用它的時候,只需要包含兩個檔案“CuTest.c CuTest.h”,然後就可以寫測試用例,進行測試了。它對用例幾乎沒有管理功能,報表輸出也非常簡單,可以用來試驗單元測試的基本想法。
  2. CUnit -- CUnit是一個輕型的C語言單元測試框架。它提供了設計、管理、執行測試用例的功能。它的報表功能比較強大,但是比較麻煩,更適合於較大一些的專案。
  3. Check -- 不錯的工具。
    在這裡(http://www.laatuk.com/tools/testing_tools.html)給出了各種軟體測試工具,沒事可以研究一下。

CUnit

這裡主要講CUnit在Linux平臺下的應用。這裡有一篇 CUnit測試工具使用 ,另一篇 C單元測試包設計與實現 講的不錯,可以看一下。CUnit的主頁是 http://cunit.sourceforge.net/index.html
CUnit以靜態庫的形式提供給使用者使用,使用者編寫程式的時候直接連結此靜態庫就可以了。它提供了一個簡單的單元測試框架,並且為常用的資料型別提供了豐富的斷言語句支援。

CUnit基本架構

                       Test Registry
                            |
             ------------------------------
             |                            |
          Suite '1'      . . . .       Suite 'N'
             |                            |
       ---------------             ---------------
       |             |             |             |
    Test '11' ... Test '1M'     Test 'N1' ... Test 'NM'

一次測試(Test Registry)可以執行多個測試包(Test Suite),而每個測試包可以包括多個測試用例(Test Case),每個測試用例又包含一個或者多個斷言類的語句。具體到程式的結構上,一次測試下轄多個Test Suite,它對應於程式中各個獨立模組;一個Suite管理多個Test Case,它對應於模組內部函式實現。每個Suite可以含有setup和teardown函式,分別在執行suite的前後呼叫。

CUnit測試模式

CUnit使用四種不同的介面,供使用者來執行測試和彙報測試結果:

  1. 自動輸出到XML檔案,     非互動式
  2. 基本擴充套件程式設計方式,        非互動式
  3. 控制檯方式,              互動式
  4. Curses圖形介面,          互動式

注意1和2是非互動式的,4只能在Unix下使用,常用console,而且console是可以人機互動的。

CUnit測試流程

使用CUnit進行測試的基本流程如下所示:

  1. 書寫代測試的函式(如果必要,需要寫suite的init/cleanup函式)
  2. 初始化Test Registry - CU_initialize_registry()
  3. 把測試包(Test Suites)加入到Test Registry - CU_add_suite()
  4. 加入測試用例(Test Case)到測試包當中 - CU_add_test()
  5. 使用適當的介面來執行測試測試程式,例如 CU_console_run_tests()
  6. 清除Test Registry - CU_cleanup_registry()

CUnit使用範例

CUnit的線上文件是 http://cunit.sourceforge.net/doc/index.html ,上面有著詳細的論述。這裡以使用自動產生XML檔案的介面為例,講述CUnit-2.1-0在Linux平臺下的使用。
我要測試的是整數求最大值的函式maxi,我使用如下檔案組織結構:

  1. func.c :定義maxi()函式
  2. test_func.c :定義測試用例和測試包
  3. run_test.c :呼叫CUnit的Automated介面執行測試
  4. Makefile :生成測試程式。

這樣組織的好處是,我們可以把各個功能分離,當要改變待測試函式的定義的時候,我們只需要修改func.c,而要增減、修改測試用例,只修改test_func.c就可以了,要使用CUnit提供的別的API,那就修改run_test.c。

它們的內容分別如下所示:

1) func.c

/**//**
 * file: func.c
 *
*/


int maxi(int i, int j)
...{
        
//return i>j?i:j;
        return i;
}

2) test_func.c

/**//**
 * file: test_func.c
 *
*/

#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<assert.h>
#include 
"CUnit/CUnit.h"
#include 
"CUnit/Automated.h" 

/**//*---- functions to be tested ------*/
extern int maxi(int i, int j); 

/**//*---- test cases ------------------*/
void testIQJ()
...{
        CU_ASSERT_EQUAL(maxi(
1,1),1);
        CU_ASSERT_EQUAL(maxi(
0,-0),0);
}

 
void testIGJ()
...{
        CU_ASSERT_EQUAL(maxi(
2,1),2);
        CU_ASSERT_EQUAL(maxi(
0,-1),0);
        CU_ASSERT_EQUAL(maxi(
-1,-2),-1);
}

 
void testILJ()
...{
        CU_ASSERT_EQUAL(maxi(
1,2),2);
        CU_ASSERT_EQUAL(maxi(
-1,0),0);
        CU_ASSERT_EQUAL(maxi(
-2,-1),-1);
}
 

CU_TestInfo testcases[] 
= ...{
        
...{"Testing i equals j:", testIQJ},
        
...{"Testing i greater than j:", testIGJ},
        
...{"Testing i less than j:", testILJ},
        CU_TEST_INFO_NULL
}
;
 

/**//*---- test suites ------------------*/
int suite_success_init(void...return 0; }
int suite_success_clean(void...return 0; } 

CU_SuiteInfo suites[] 
= ...{
        
...{"Testing the function maxi:", suite_success_init, suite_success_clean, testcases},
        CU_SUITE_INFO_NULL
}
;
 

/**//*---- setting enviroment -----------*/

void AddTests(void)
...{
        assert(NULL 
!= CU_get_registry());
        assert(
!CU_is_test_running());
        
/**//* shortcut regitry */

        
if(CUE_SUCCESS != CU_register_suites(suites))...{
                fprintf(stderr, 
"Register suites failed - %s ", CU_get_error_msg());
                exit(EXIT_FAILURE);
        }

}

 3) run_test.c

/**//**
 * file: run_test.c
 *
*/


#include 
<stdio.h>
#include 
<stdlib.h>
#include 
<assert.h>

int main( int argc, char *argv[] )
...{
       
if(CU_initialize_registry())...{
                fprintf(stderr, 
" Initialization of Test Registry failed. ");
                exit(EXIT_FAILURE);
        }
else...{
                AddTests();
                CU_set_output_filename(
"TestMax");
                CU_list_tests_to_file();
                CU_automated_run_tests();
                CU_cleanup_registry();
        }

        
return 0;
}

4) Makefile 

INC=-I/home/lirui/local/include
LIB
=-L/home/lirui/local/lib

all: func
.c test_func.c run_test.c
        gcc -o test 
$(INC) $(LIB)-lcunit -lcurses -static $^

由於CUnit是以庫的形式提供的,所以我們在編譯和連結的時候需要指明標頭檔案和庫所在的位置,又由於使用了Curses庫,所以也要指定這個。

測試報告

 執行上面產生的test程式,會在當前目錄下產生兩個xml檔案:

  1. TestMax-Listing.xml :對測試用例的報告
  2. 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檔案就可以了。

1) TestMax-Listing.xml在IE當中顯示如下:

list cases

2) TestMax-Results.xml在IE當中顯示如下:

results

相關文章