1. 程式人生 > >單元測試工具 CUnit 簡介

單元測試工具 CUnit 簡介

 1.CUnit簡介

  1.1 CUnit簡要描述

  CUnit是一個編寫、管理及執行c語言單元測試的系統。它使用一個簡單的框架來構建測試結構,併為普通資料結構的測試提供豐富的斷言。此外,CUnit為測試的執行和結果檢視提供了許多不同的介面,包括自動測試模式和可互動的控制檯模式。

  其常用的資料型別和函式在以下標頭檔案中宣告:

  標頭檔案 內容描述

  <CUnit/CUnit.h> 包括測試用例中常用的巨集定義和框架中其它標頭檔案

  <CUnit/CUError.h> 錯誤處理函式及錯誤編號

  <CUnit/TestDB.h> 測試註冊簿、測試包和測試用例的操作及資料型別

  <CUnit/TestRun.h> 測試執行及結果檢索的操作及資料型別

  <CUnit/Automated.h> 輸出Xml結果相關的自動模式介面

  <CUnit/Basic.h> 非互動模式的基本模式介面

  <CUnit/Console.h> 互動模式的介面

  1.2 測試框架結構

  CUnit核心框架為測試註冊簿、測試包和測試用例的管理提供了基本支援,它提供的介面可以使使用者和測試框架互動,方便測試的執行和測試結果的檢視。CUnit被組織成一個常見的單元測試框架,其結構如下:

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


  測試用例被打包成測試包,並被註冊到當前活動的測試註冊簿中。測試包的裝載和解除安裝函式在測試執行前後被自動呼叫。所有的測試包和測試用例可以一鍵執行,也可以選擇相應的測試包或測試用例來執行測試。


1.3 基本使用方法

  使用CUnit框架的常用流程如下:

  編寫測試用例,如果有必要須對測試包進行初始化或者清理

  初始化測試註冊簿 CU_initialize_registry()

  向註冊簿中註冊測試包 CU_add_suite()

  向測試包中新增測試用例 CU_add_test()

  使用合適的測試模式執行測試CU_automated(basic/console/curses)_run_tests()

  清理測試註冊簿 CU_cleanup_registry()

  1.4 Linux下CUnit的安裝

The usual sequence of steps should succeed in building and installing CUnit:
aclocal  (if necessary)
autoconf (if necessary)
automake (if necessary)
chmod u+x configure (if necessary)
./configure --prefix <Your choice of directory for installation>
make
make install
What's installed:
libcunit.a (Library file)
CUnit Header files
DTD and XSL files supporting xml output files in share directory
Man Pages in relevant man directories under the installation path.
HTML users guide in the doc subdirectory of the installation path.
Example & test programs in the share subdirectory of the install path.

  2. 編寫CUnit測試用例

  2.1 測試用例函式的命名

  CUnit中對於測試函式的定義沒有嚴格的規範,一個常用的示例如下:

  int maxi(int i1, int i2)
  {
  return (i1 > i2)  i1 : i2;
  }
  void test_maxi(void)
  {
  CU_ASSERT(maxi(0,2) == 2);
  CU_ASSERT(maxi(0,-2) == 0);
  CU_ASSERT(maxi(2,2) == 2);
  }

  2.2 CUnit中的斷言

  CUnit為邏輯條件測試提供了一系列的斷言。測試框架會跟蹤這些斷言的通過或失敗,當測試執行完成時便可看到結果。

  每一個斷言測試一個邏輯條件,條件的值為CU_FALSE表示斷言失敗。對於測試失敗,測試將會繼續執行,除非使用者選擇“xxx_FATAL”型別的斷言,這種情況下該測試函式將會失敗並立即返回。FATAL型別的斷言應該和警告一塊使用!一旦FATAL型別的斷言導致測試失敗,測試函式將沒有機會做清理工作,普通的清理函式不會起任何作用。

  另外一些特殊的斷言被註冊為“pass”或“fail”,它們不是用來做邏輯測試,而是用來測試流程控制或者其他條件測試的。例如:

void test_longjmp(void)
{
jmp_buf buf;
int i;
i = setjmp(buf);
if (i == 0) {
run_other_func();
CU_PASS("run_other_func() succeeded.");
}
else
CU_FAIL("run_other_func() issued longjmp.");
}

  所有的斷言被定義在<CUnit/CUnit.h>

  3. 測試註冊簿

  3.1 常用相關函式

#include  <CUnit/TestDB.h>
typedef struct CU_TestRegistry
typedef CU_TestRegistry* CU_pTestRegistry
CU_ErrorCode CU_initialize_registry(void)
void CU_cleanup_registry(void)
CU_BOOL CU_registry_initialized(void)
CU_pTestRegistry CU_get_registry(void)
CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry)
CU_pTestRegistry CU_create_new_registry(void)
void CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry)

  3.2 註冊簿內部結構體

  測試註冊簿是測試包和相關測試用例的倉庫。當用戶新增測試包或測試用例時,CUnit維護當前活動的測試註冊簿的狀態更新,當用戶選擇執行所有測試用例時,當前活動的註冊簿中所有的測試包均被執行。

  測試註冊簿結構在<CUnit_TestDB.h>中定義,它包括所有測試包的數量、所有測試用例的數量以及一個指向該註冊簿中測試包連結串列的指標:

typedef struct CU_TestRegistry
{
unsigned int uiNumberOfSuites;
unsigned int uiNumberOfTests;
CU_pSuite    pSuite;
} CU_TestRegistry;
typedef CU_TestRegistry* CU_pTestRegistry;

  使用者通常只需在使用前初始化測試註冊簿,之後做清除工作即可。此外CUnit還提供了一些必要的註冊簿操作函式。

3.5 與註冊簿相關的其它函式

CU_pTestRegistry CU_get_registry(void)
CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry)
CU_pTestRegistry CU_create_new_registry(void)
void CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry)

  4. 測試包及測試用例的管理

  4.1 相關函式及結構

#include <CUnit/TestDB.h>
typedef struct CU_Suite
typedef CU_Suite* CU_pSuite
typedef struct CU_Test
typedef CU_Test* CU_pTest
typedef void (*CU_TestFunc)(void)
typedef int (*CU_InitializeFunc)(void)
typedef int (*CU_CleanupFunc)(void)
CU_pSuite CU_add_suite(const char* strName,CU_InitializeFunc pInit,CU_CleanupFunc pClean);
CU_pTest   CU_add_test(CU_pSuite pSuite,const char* strName,CU_TestFunc pTestFunc);
typedef struct CU_TestInfo
typedef struct CU_SuiteInfo
CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[]);
CU_ErrorCode CU_register_nsuites(int suite_count, ...);
CU_ErrorCode CU_set_suite_active(CU_pSuite pSuite, CU_BOOL fNewActive)
CU_ErrorCode CU_set_test_active(CU_pTest, CU_BOOL fNewActive)
CU_ErrorCode CU_set_suite_name(CU_pSuite pSuite, const char *strNewName)
CU_ErrorCode CU_set_suite_initfunc(CU_pSuite pSuite, CU_InitializeFunc pNewInit)
CU_ErrorCode CU_set_suite_cleanupfunc(CU_pSuite pSuite, CU_CleanupFunc pNewClean)
CU_ErrorCode CU_set_test_name(CU_pTest pTest, const char *strNewName)
CU_ErrorCode CU_set_test_func(CU_pTest pTest, CU_TestFunc pNewFunc)
CU_pSuite CU_get_suite(const char* strName)
CU_pSuite CU_get_suite_at_pos(unsigned int pos)
unsigned int CU_get_suite_pos(CU_pSuite pSuite)
unsigned int CU_get_suite_pos_by_name(const char* strName)
CU_pTest CU_get_test(CU_pSuite pSuite, const char *strName)
CU_pTest CU_get_test_at_pos(CU_pSuite pSuite, unsigned int pos)
unsigned int CU_get_test_pos(CU_pSuite pSuite, CU_pTest pTest)
unsigned int CU_get_test_pos_by_name(CU_pSuite pSuite, const char *strName)

  4.2 註冊測試包

  CU_pSuite CU_add_suite(const char* strName, CU_InitializeFunc pInit, CU_CleanupFunc pClean)

  建立一個測試包,該測試包擁有自己特定的名字、初始化函式及清理函式。該測試包被註冊到一個測試註冊簿,該註冊簿在新增任意測試包之前須初始化。當前版本不支援獨立於註冊簿之外的測試包的建立,該函式不應該在測試執行期間被呼叫。

  在註冊簿中,推薦每個測試包有唯一的名字,這樣可以通過名字查詢測試包。在上述函式中,測試包的初始化函式和清理函式是可選的,如果不需要這些函式可以傳引數NULL。

  該函式返回值分為五種:

  CUE_SUCCESS suite creation was successful.

  CUE_NOREGISTRY Error the registry has not been initialized.

  CUE_NO_SUITENAME ErrorstrName was

  NULL.CUE_DUP_SUITE Warning the suite's name was not unique.

  CUE_NOMEMORY Error memory allocation failed.

  4.3 新增測試用例到測試包

  CU_pTest CU_add_test(CU_pSuite pSuite, const char* strName, CU_TestFunc pTestFunc)

  建立一個測試用例,該測試包擁有自己特定的名字、初始化函式及清理函式。該測試用例被打包到一個測試包,當前版本不支援獨立於測試包之外的建立,該函式不應該在測試執行期間被呼叫。

  在單個測試包中,推薦每個測試用例有唯一的名字,這樣可以通過名字查詢測試用例。引數接受一個測試函式的函式指標,不可以為空,當執行測試時,該函式將被呼叫。測試函式沒有引數也沒有返回值。

  該函式返回值分為7種:

  CUE_SUCCESS suite creation was successful.

  CUE_NOREGISTRY Error: the registry has not been initialized.

  CUE_NOSUITE Error: the specified suite was NULL or invalid.

  CUE_NO_TESTNAME Error: strName was NULL.

  CUE_NO_TEST Error: pTestFunc was NULL or invalid.

  CUE_DUP_TEST Warning: the test's name was not unique.

  CUE_NOMEMORY Error: memory allocation failed.

  4.4 測試包及測試用例管理的快捷方法

  CUnit定義了許多類似如下的巨集:

  #define CU_ADD_TEST(suite, test) (CU_add_test(suite, #test, (CU_TestFunc)test))

  這些巨集可以針對測試函式名字,自動生成擁有惟一名字的測試用例,並將該測試用例新增到指定的測試包,使用者應該驗證返回值以保證正常新增。

  CU_ErrorCode CU_register_suites(CU_SuiteInfo suite_info[])

  CU_ErrorCode CU_register_nsuites(int suite_count, ...)

  對於擁有很多測試包和測試用例的大型測試結構,管理測試包和測試用例的關聯和註冊是相當乏味和易出錯的。CUnit提供了一個特殊的註冊系統來幫助使用者管理測試包和測試用例。這個系統將測試包的註冊和測試用例的關聯集中起來,以縮減使用者的程式碼量。

  CU_TestInfo例項可以將許多測試用例集中放到一個數組,以便於關聯到一個測試包。每個陣列元素包括一個惟一的名字和測試函式。該陣列必須以CU_TEST_INFO_NULL結尾。

CU_TestInfo test_array1[] = {
{ "testname1", test_func1 },
{ "testname2", test_func2 },
{ "testname3", test_func3 },
CU_TEST_INFO_NULL,
};

  同樣的,CU_SuiteInfo也提供類似的封裝功能,它將測試包名字、測試包初始化函式、清理函式和其關聯的測試用例封裝起來。

CU_SuiteInfo suites[] = {
{ "suitename1", suite1_init-func, suite1_cleanup_func, test_array1 },
{ "suitename2", suite2_init-func, suite2_cleanup_func, test_array2 },
CU_SUITE_INFO_NULL,
};

  這樣,我們將整個註冊流程簡化為:

  CU_ErrorCode error = CU_register_suites(suites);

  或者

  CU_ErrorCode error = CU_register_nsuites(2, suites1, suites2);

  這些函式的返回值和包註冊函式、測試用例關聯函式相同

4.5 設定當前活動測試包和測試用例

  CU_ErrorCode CU_set_suite_active(CU_pSuite pSuite, CU_BOOL fNewActive)

  CU_ErrorCode CU_set_test_active(CU_pTest pTest, CU_BOOL fNewActive)

  這些函式被用來測試包測試用例為當前活動包和當前活動測試用例,一個測試包或者測試用例在執行測試時不會被執行,除非使用者將它設定為是當前活動的。所有的測試包和測試用例在建立時被預設設定為活動的。當前活動狀態可以通過pSuite->fActive或pTest->fActive獲取。這樣,客戶端就有能力動態地選擇測試用例去執行測試。如果引數對應的包或者用例不存在則返回CUE_NOSUIT或CUI_NOTEST。

  4.6 修改測試包和測試用例的屬性

CU_ErrorCode CU_set_suite_name(CU_pSuite pSuite, const char *strNewName)
CU_ErrorCode CU_set_test_name(CU_pTest pTest, const char *strNewName)
CU_ErrorCode CU_set_suite_initfunc(CU_pSuite pSuite, CU_InitializeFunc pNewInit)
CU_ErrorCode CU_set_suite_cleanupfunc(CU_pSuite pSuite, CU_CleanupFunc pNewClean)
CU_ErrorCode CU_set_test_func(CU_pTest pTest, CU_TestFunc pNewFunc)

  4.7 測試包和測試用例的查詢

  大多數情況下,客戶端可以通過註冊測試包和關聯測試用例獲取它們的引用。有時候客戶端可能需要有能力去檢索某個測試包或測試用例的引用。CUnit提供給客戶端獲取某個測試包或測試用例資訊的能力。

CU_pSuite CU_get_suite(const char* strName)
CU_pSuite CU_get_suite_at_pos(unsigned int pos)
unsigned int CU_get_suite_pos(CU_pSuite pSuite)
unsigned int CU_get_suite_pos_by_name(const char* strName)

  這些函式使使用者查詢註冊到當前活動註冊簿中的測試包。可以通過傳入名字、位置引數來獲取測試包,如果該測試包不存在,則返回NULL。位置引數從1開始到註冊簿中的測試包數。按名字查詢的方式只返回測試包連結串列中的第一個測試包。如果註冊簿沒有初始化則錯誤碼為CUE_NOREGISTRY,相應的,如果按名字查詢的包不存在,錯誤碼為CUE_NO_SUITENAME且返回NULL。

CU_pTest CU_get_test(CU_pSuite pSuite, const char *strName)
CU_pTest CU_get_test_at_pos(CU_pSuite pSuite, unsigned int pos)
unsigned int CU_get_test_pos(CU_pSuite pSuite, CU_pTest pTest)
unsigned int CU_get_test_pos_by_name(CU_pSuite pSuite, const char *strName)

  如上函式和測試包查詢類似。

  5. 執行測試

  5.1 常用相關函式

#include <CUnit/Automated.h>
void         CU_automated_run_tests(void)
CU_ErrorCode CU_list_tests_to_file(void)
void         CU_set_output_filename(const char* szFilenameRoot)
#include <CUnit/Basic.h>
typedef enum     CU_BasicRunMode
CU_ErrorCode     CU_basic_run_tests(void)
CU_ErrorCode     CU_basic_run_suite(CU_pSuite pSuite)
CU_ErrorCode     CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest)
void             CU_basic_set_mode(CU_BasicRunMode mode)
CU_BasicRunMode CU_basic_get_mode(void)
void             CU_basic_show_failures(CU_pFailureRecord pFailure)
#include <CUnit/Console.h>
void CU_console_run_tests(void)
#include <CUnit/CUCurses.h>
void CU_curses_run_tests(void)
#include <CUnit/TestRun.h>
unsigned int CU_get_number_of_suites_run(void)
unsigned int CU_get_number_of_suites_failed(void)
unsigned int CU_get_number_of_tests_run(void)
unsigned int CU_get_number_of_tests_failed(void)
unsigned int CU_get_number_of_asserts(void)
unsigned int CU_get_number_of_successes(void)
unsigned int CU_get_number_of_failures(void)
typedef struct CU_RunSummary
typedef CU_Runsummary* CU_pRunSummary
const CU_pRunSummary CU_get_run_summary(void)
typedef struct CU_FailureRecord
typedef CU_FailureRecord*  CU_pFailureRecord
const CU_pFailureRecord CU_get_failure_list(void)
unsigned int CU_get_number_of_failure_records(void)
void CU_set_fail_on_inactive(CU_BOOL new_inactive)
CU_BOOL CU_get_fail_on_inactive(void)

  5.2 自動模式

  CUnit支援執行註冊簿中所有的測試用例,它同時支援單獨執行某個測試包或測試用例。 CUnit框架會在每個測試執行期間跟蹤測試包、用例、斷言以及斷言通過和失敗的數量。需要注意的是,每次測試初始化(即便是初始化失敗)前次的測試結果都會被清空。,如果客戶端想排除某些用例以做某個特殊測試,單個測試包或測試用例可以被設定為非活動。

  自動模式介面提供非互動模式測試,使用者初始化測試並執行,結果被匯出到一個XML檔案,所有的測試註冊簿和測試包均可以被匯出到XML檔案。自動模式介面包括如下函式:

  void CU_automated_run_tests(void) 該函式執行註冊簿中所有活動的的測試包,測試結果被輸出到一個名字為ROOT-Results的XML檔案。ROOT可以通過 CU_set_output_filename()設定,否則使用預設檔名 CUnitAutomated-Results.xml。需要指出的是,如果不設定一個獨特的名字,測試結果會被覆蓋。

  CU_ErrorCode CU_list_tests_to_file(void) 該函式在檔案中列出所有註冊的測試包及相關聯的測試用例。列表檔名為ROOT-Listing.XML。名字ROOT可以通過 CU_set_output_filename()設定,否則預設檔名CUnitAutomated便被啟用,同樣的,如果不區分名字,該列表檔案將會被覆蓋。需要指出的是,如果使用者需要一個列表檔案,他必須顯示地去呼叫該介面函式。

  void CU_set_output_filename(const char* szFilenameRoot) 這個函式用於設定輸出結果或列表檔案的檔名,該引數後面會相應的追加-Results.xml或-Listing.xml。