【單元測試】CUnit用戶手冊(中文)

聲明:本文是對 CUnit Users Guide 的中文翻譯。網上也有看到一些中文翻譯,但看起來更像是機器翻譯的,有很多地方不通順,爲此,自己花了幾天重新翻譯了一遍,以供他人查閱。如有翻譯不妥之處,歡迎留言指出。

版本:截止書稿時,CUnit 的最新版本是CUnit-2.1-3,本文正是基於 CUnit-2.1-3 版本的 CUnit Users Guide 的中文翻譯,原版 Users Guide 在 CUnit-2.1-3 源碼目錄 doc/index.html 中。注意:未來官方版本隨時可能變更,此中文翻譯可能跟官網英文會有差異,務必注意版本區別。

1. CUnit單元測試簡介

1.1. 描述

CUnit是一個在C語言中編寫、管理和運行單元測試的系統,它往往被編譯成庫(靜態庫或動態庫)的形式提供給用戶測試代碼進行鏈接。

CUnit使用一個簡單的框架作爲構建測試結構,它提供了一組豐富的斷言來測試常見的數據類型。此外,它還提供了多種不同的接口用來運行測試和報告結果。這包括由代碼控制的測試和報告的自動化接口,以及允許由用戶動態運行測試和查看結果的交互式接口。

以下頭文件對常見用戶會用到的數據類型和函數做了聲明:

Header File Description
#include <CUnit/CUnit.h> ASSERT(斷言)宏在測試案例中使用,包括其他框架的頭文件。
#include <CUnit/CUError.h> 錯誤處理函數和數據類型。
被CUnit.h文件自動包含。
#include <CUnit/TestDB.h> 數據類型的定義和測試套件、測試用例的註冊功能接口。
被CUnit.h文件自動包含。
#include <CUnit/TestRun.h> 數據類型的定義和運行測試、檢索結果的功能接口。
被CUnit.h文件自動包含。
#include <CUnit/Automated.h> 自動XML輸出接口。
#include <CUnit/Basic.h> 一個非交互的輸出到標準輸出(stdout)的基本接口。
#include <CUnit/Console.h> 交互式控制檯界面。
#include <CUnit/CUCurses.h> 交互式控制檯接口(* nix平臺)。
#include <CUnit/Win.h> Windows界面(尚未實施)。

1.2. 結構

CUnit是平臺無關的框架與各種用戶接口的組合。核心框架爲管理測試註冊表,套件和測試用例提供了基本支持。用戶接口便於與框架交互以運行測試和查看結果。

CUnit的組織結構與傳統的單元測試框架類似:

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

單獨的測試用例(Test)被打包到套件(Suite)中,這些套件又被註冊到活動測試註冊表(Test Registry)裏。每個套件都有自己的構造和析構函數,這兩個函數將在運行套件測試之前和之後被自動調用。註冊表中的所有套件/測試用例,可以通過調用一個函數執行全部測試,也可以有選擇性地執行部分測試。

1.3. 通常用法

使用CUnit框架的典型步驟是:

  1. 編寫待測函數(必要時,還得編寫套件的 init/cleanup 函數)。
  2. 初始化測試註冊表(Test Registry) - CU_initialize_registry()
  3. 將套件(Suite)添加到測試註冊表 - CU_add_suite()
  4. 將測試用例(Test)加到套件裏 - CU_add_test()
  5. 調用合適的接口函數執行測試,例如CU_console_run_tests
  6. 清除測試註冊表 - CU_cleanup_registry

1.4. CUnit第2版API變更

在Cunit中所有公共函數名現在都以 ‘CU_’ 作爲前綴。這有利於避免與用戶代碼中的函數名發生衝突。需要注意的是,早期版本的CUnit使用的是不同的函數名,而且沒有這個前綴。舊的API名稱已棄用但仍受支持。要使用舊的名稱,用戶代碼必須使用USE_DEPRECATED_CUNIT_NAMES的宏定義進行編譯。

那些已棄用的API函數在文檔相應部位都有進行描述。

2. 編寫CUnit測試用例

2.1. 測試函數

CUnit測試用例是一個C函數,其函數簽名(語法格式)爲:

void test_func(void)

測試函數除了不應該修改CUnit框架(比如:添加套件或測試用例,修改測試註冊表,或啓動測試執行)之外,它的內容沒有任何限制。測試函數可以調用其他函數(當然也不可以修改框架)。當執行測試時,已註冊的測試用例對應的測試函數將會被調用。

以下例子是對「返回2個整數中的最大數值的函數」進行測試的例程:

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 版本斷言失敗,測試函數就沒有機會執行自身的清理工作。但是,普通套件的清理函數不受影響,兩個版本的斷言都會執行套件的清理函數。

還有一些特殊的斷言,用於在不執行邏輯測試的情況下,向框架註冊通過或失敗。這對於測試控制流或其他不需要邏輯測試的情況都非常有用:

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斷言,這些斷言將被計算到調用它們的測試函數中,也可以使用FATAL版本的斷言 - 如果斷言失敗將中止原始測試函數和整個調用鏈。

CUnit定義的斷言:

#include <CUnit/CUnit.h>

斷言 描述
CU_ASSERT(int expression)
CU_ASSERT_FATAL(int expression)
CU_TEST(int expression)
CU_TEST_FATAL(int expression)
Assert that expression is TRUE (non-zero)
CU_ASSERT_TRUE(value)
CU_ASSERT_TRUE_FATAL(value)
Assert that value is TRUE (non-zero)
CU_ASSERT_FALSE(value)
CU_ASSERT_FALSE_FATAL(value)
Assert that value is FALSE (zero)
CU_ASSERT_EQUAL(actual, expected)
CU_ASSERT_EQUAL_FATAL(actual, expected)
Assert that actual = = expected
CU_ASSERT_NOT_EQUAL(actual, expected))
CU_ASSERT_NOT_EQUAL_FATAL(actual, expected)
Assert that actual != expected
CU_ASSERT_PTR_EQUAL(actual, expected)
CU_ASSERT_PTR_EQUAL_FATAL(actual, expected)
Assert that pointers actual = = expected
CU_ASSERT_PTR_NOT_EQUAL(actual, expected)
CU_ASSERT_PTR_NOT_EQUAL_FATAL(actual, expected)
Assert that pointers actual != expected
CU_ASSERT_PTR_NULL(value)
CU_ASSERT_PTR_NULL_FATAL(value)
Assert that pointer value == NULL
CU_ASSERT_PTR_NOT_NULL(value)
CU_ASSERT_PTR_NOT_NULL_FATAL(value)
Assert that pointer value != NULL
CU_ASSERT_STRING_EQUAL(actual, expected)
CU_ASSERT_STRING_EQUAL_FATAL(actual, expected)
Assert that strings actual and expected are equivalent
CU_ASSERT_STRING_NOT_EQUAL(actual, expected)
CU_ASSERT_STRING_NOT_EQUAL_FATAL(actual, expected)
Assert that strings actual and expected differ
CU_ASSERT_NSTRING_EQUAL(actual, expected, count)
CU_ASSERT_NSTRING_EQUAL_FATAL(actual, expected, count)
Assert that 1st count chars of actual and expected are the same
CU_ASSERT_NSTRING_NOT_EQUAL(actual, expected, count)
CU_ASSERT_NSTRING_NOT_EQUAL_FATAL(actual, expected, count)
Assert that 1st count chars of actual and expected differ
CU_ASSERT_DOUBLE_EQUAL(actual, expected, granularity)
CU_ASSERT_DOUBLE_EQUAL_FATAL(actual, expected, granularity)
Assert that (actual - expected) <= (granularity)
Math library must be linked in for this assertion.
CU_ASSERT_DOUBLE_NOT_EQUAL(actual, expected, granularity)
CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL(actual, expected, granularity)
Assert that (actual - expected) > (granularity)
Math library must be linked in for this assertion.
CU_PASS(message) Register a passing assertion with the specified message. No logical test is performed.
CU_FAIL(message)
CU_FAIL_FATAL(message)
Register a failed assertion with the specified message. No logical test is performed.

2.3. 已棄用的V1版本的斷言

從版本2開始,不推薦使用以下斷言。要使用這些斷言,用戶代碼必須使用USE_DEPRECATED_CUNIT_NAMES宏定義進行編譯。需要注意的是,它們的行爲與版本1中的行爲是相同的(失敗時發出’return’語句)。

#include <CUnit/CUnit.h>

棄用名稱 等效新名稱
ASSERT CU_ASSERT_FATAL
ASSERT_TRUE CU_ASSERT_TRUE_FATAL
ASSERT_FALSE CU_ASSERT_FALSE_FATAL
ASSERT_EQUAL CU_ASSERT_EQUAL_FATAL
ASSERT_NOT_EQUAL CU_ASSERT_NOT_EQUAL_FATAL
ASSERT_PTR_EQUAL CU_ASSERT_PTR_EQUAL_FATAL
ASSERT_PTR_NOT_EQUAL CU_ASSERT_PTR_NOT_EQUAL_FATAL
ASSERT_PTR_NULL CU_ASSERT_PTR_NULL_FATAL
ASSERT_PTR_NOT_NULL CU_ASSERT_PTR_NOT_NULL_FATAL
ASSERT_STRING_EQUAL CU_ASSERT_STRING_EQUAL_FATAL
ASSERT_STRING_NOT_EQUAL CU_ASSERT_STRING_NOT_EQUAL_FATAL
ASSERT_NSTRING_EQUAL CU_ASSERT_NSTRING_EQUAL_FATAL
ASSERT_NSTRING_NOT_EQUAL CU_ASSERT_NSTRING_NOT_EQUAL_FATAL
ASSERT_DOUBLE_EQUAL CU_ASSERT_DOUBLE_EQUAL_FATAL
ASSERT_DOUBLE_NOT_EQUAL CU_ASSERT_DOUBLE_NOT_EQUAL_FATAL

3. 測試註冊表

3.1. 摘要

#include <CUnit/TestDB.h> (included automatically by <CUnit/CUnit.h>)

  typedef struct CU_TestRegistry
  typedef CU_TestRegistry*  CU_pTestRegistry

  CU_ErrorCode     CU_initialize_registry(void)
  void             CU_cleanup_registry(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測試註冊表是<CUnit/TestDB.h>中聲明的數據結構CU_TestRegistry。它包括存儲在註冊表中的套件和測試用例的總數,以及一個指向已註冊的套件鏈表的頭指針。

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

typedef CU_TestRegistry* CU_pTestRegistry;

用戶通常只需要在使用前初始化註冊表,使用後進行清理。但是,在必要時也可以使用其他一些函數操作註冊表。

3.3. 註冊表初始化

  • CU_ErrorCode CU_initialize_registry(void)

    CUnit測試註冊表在使用之前必須先初始化。用戶在調用任何其他CUnit函數之前應先調用CU_initialize_registry()。不這樣做可能會導致系統崩潰。如果多次調用此函數,已存在的註冊表都將被清除(即銷燬),然後才創建一個新的註冊表。在執行測試期間(即測試函數或套件初始化/清除函數中)不應該調用此函數。

    返回值:

    返回值 描述
    CUE_SUCCESS 初始化成功
    CUE_NOMEMORY 內存分配失敗
  • CU_BOOL CU_registry_initialized(void)

    此函數可用於檢查註冊表是否已初始化。如果註冊表設置分佈在多個文件上,爲了確保註冊表已準備好進行測試註冊,這時會很有用。

3.4. 註冊表清除

  • void CU_cleanup_registry(void)

    當測試完成後,用戶應調用此函數來清理和釋放框架使用的內存。這應該是最後一個調用的CUnit函數(除非使用CU_initialize_registry()或CU_set_registry()恢復測試註冊表)。

    未調用 CU_cleanup_registry() 將導致內存泄漏。它可以被多次調用,且不會報錯。注意,這個函數會銷燬註冊表中的所有套件(以及相關聯的測試用例)。清理註冊表之後,指向已註冊的套件和測試用例的指針不應該再被引用。在執行測試期間(即測試函數或套件初始化/清除函數中)不應該調用此函數。

    調用 CU_cleanup_registry() 只會影響CUnit框架維護的內部CU_TestRegistry。那些隸屬於用戶的的測試註冊表,用戶有責任自己銷燬。可以顯式地通過調用CU_destroy_existing_registry()來完成,也可以隱式地通過先調用CU_set_registry()激活註冊表,再調用CU_cleanup_registry()來完成。

3.5. 其他註冊表函數

其他註冊表函數主要用於內部和測試目的。但是,一般用戶可以瞭解它們的用途,並且應該知道它們。

其中包括:

  • CU_pTestRegistry CU_get_registry(void)

    返回一個指向活動測試註冊表的指針。註冊表是一個CU_TestRegistry數據類型的變量。不建議直接操作內部測試註冊表,應該使用API函數操作。框架維護註冊表的所有權,因此當調用CU_cleanup_registry()或CU_initialize_registry()函數之後,該函數返回的指針將會失效。

  • CU_pTestRegistry CU_set_registry(CU_pTestRegistry pTestRegistry)

    用指定的註冊表替換活動註冊表,返回指向之前註冊表的指針。調用者有責任銷燬舊的註冊表,這可以通過爲返回的指針調用CU_destroy_existing_registry()來顯式地完成。或者,也可以先使用CU_set_registry()激活註冊表,再調用CU_cleanup_registry()隱式地銷燬註冊表。應注意不要顯式地銷燬被激活的註冊表,這會導致同一個內存塊被多次釋放,很可能會引起崩潰。

  • CU_pTestRegistry CU_create_new_registry(void)

    創建一個新的註冊表並返回一個指向它的指針。新的註冊表不包含任何的套件或測試用例。調用者有責任通過前面描述的機制之一銷燬新的註冊表。

  • void CU_destroy_existing_registry(CU_pTestRegistry* ppRegistry)

    銷燬並釋放指定測試註冊表的所有內存,包括所有已註冊的套件和測試用例。對於被設置爲激活狀態的註冊表(例如,CU_get_registry()返回的CU_pTestRegistry指針),不應調用此函數。這會導致在調用CU_cleanup_registry()時多次釋放相同的內存。ppRegistry可能不爲NULL,但指針內容可以爲空。在這種情況下,該函數不起作用。請注意,本函數返回時會將 *ppRegistry 設置爲NULL。

3.6. 已棄用的V1版本的數據類型和函數

從版本2開始,不推薦使用以下數據類型和函數。要使用這些已棄用的名稱,用戶代碼必須使用USE_DEPRECATED_CUNIT_NAMES宏定義進行編譯。

#include <CUnit/TestDB.h> (included automatically by CUnit/CUnit.h>).

棄用名稱 等效新名稱
_TestRegistry CU_TestRegistry
_TestRegistry.uiNumberOfGroups
PTestRegistry->uiNumberOfGroups
CU_TestRegistry.uiNumberOfSuites
CU_pTestRegistry->uiNumberOfSuites
_TestRegistry.pGroup
PTestRegistry->pGroup
CU_TestRegistry.pSuite
CU_pTestRegistry->pSuite
PTestRegistry CU_pTestRegistry
initialize_registry() CU_initialize_registry()
cleanup_registry() CU_cleanup_registry()
get_registry() CU_get_registry()
set_registry() CU_set_registry()

4. 管理測試用例和套件

爲了讓CUnit運行一個測試用例,必須將其添加到由測試註冊表註冊的測試集(套件)中。

4.1. 摘要

#include <CUnit/TestDB.h> (included automatically by <CUnit/CUnit.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)

    創建一個新的測試用例集(套件),它具有制定的名稱,初始化函數(或稱爲構造函數)和清理函數(或稱爲析構函數)。新的套件註冊在(隸屬於)測試註冊表,因此在添加套件之前,必須先對註冊表進行初始化。目前的實現並不支持創建獨立於註冊表的套件。在執行測試期間(即測試函數或套件初始化/清除函數中)不應該調用此函數。

    建議註冊表中的每個套件的名稱是唯一的。這有助於按名稱查找套件,僅查找符合給定名稱的第一個套件。初始化和清理函數是可選的,並作爲指針傳遞給這些函數,即運行套件中的測試用例之前和之後要調用的函數。這使得套件可以 setUp 和 tearDown 臨時 fixtures(固定環境),以支持運行測試用例。這些函數沒有參數,如果他們執行成功應返回零(否則返回非零值)。如果套件不需要這些函數中的一個或兩個,可以傳遞NULL給CU_add_suite()。

    **解釋下什麼是 setUp、tearDown 和 fixtures(本段文字原文中沒有)。**Test Fixture 是指一個測試運行所需的固定環境,準確的定義:The test fixture is everything we need to have in place to exercise the SUT. 在進行測試時,我們通常需要把環境設置成已知狀態(如創建對象、獲取資源等)來創建測試,每次測試開始時都處於一個固定的初始狀態(通過setUp實現);測試結果後需要將測試狀態還原(通過tearDown實現)。setUp用於測試用例執行前的初始化工作,tearDown與之對應,用於測試用例執行後的善後工作。

    該函數返回一個指向新套件的指針,這是向套件添加測試用例時所需要的。如果發生錯誤,則返回NULL,框架錯誤代碼被設置爲下列之一:

    返回值 描述
    CUE_SUCCESS 創建成功
    CUE_NOREGISTRY 註冊表未初始化
    CUE_NO_SUITENAME 套件名(strName)爲空NULL
    CUE_DUP_SUITE 套件名不唯一
    CUE_NOMEMORY 內存申請失

4.3. 往套件中添加測試用例

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

    創建一個具有指定名稱和測試功能的新測試用例,並將其註冊到指定的套件中。所指定的套件必須是已經調用CU_add_suite()創建好了的。目前的實現並不支持創建獨立於套件的測試用例。在執行測試期間(即測試函數或套件初始化/清除函數中)不應該調用此函數。

    建議同一個套件中的每個測試用例的名稱是唯一的。這有助於按名稱查找測試用例,僅查找符合給定名稱的第一個測試用例。測試函數不能爲空,須指向一個運行測試時被調用的函數。測試函數既沒有參數也沒有返回值。

    該函數返回一個指向新測試用例的指針。如果創建測試用例過程中發生錯誤,則返回NULL,框架錯誤代碼被設置爲下列之一:

    返回值 描述
    CUE_SUCCESS 創建成功
    CUE_NOREGISTRY 註冊表未初始化
    CUE_NOSUITE 指定的套件無效
    CUE_NO_TESTNAME 測試用例名稱(strName)爲空NULL
    CUE_NO_TEST 測試函數指針無效
    CUE_DUP_TEST 測試用例名不唯一
    CUE_NOMEMORY 內存申請失敗

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

  • #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實例數組(在<CUnit/TestDB.h>中定義):

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

    每個數組元素包含一個測試用例的(唯一)名稱和測試函數。該數組的結束元素必須爲NULL,宏CU_TEST_INFO_NULL很方便地定義了該元素。單個CU_TestInfo數組中包含的測試用例形成了一個測試集,這個測試集將被註冊到同一個套件中。

    然後在一個或多個CU_SuiteInfo實例數組中定義套件信息(在<CUnit/TestDB.h>中定義):

    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_TestInfo數組。同理,如果給定的套件不需要初始化或清除函數則可以設置爲NULL。該數組必須以全NULL的元素作爲結束,可以使用宏CU_SUITE_INFO_NULL。

    然後,在CU_SuiteInfo數組中定義的所有套件都可以在一條語句中註冊:

    CU_ErrorCode error = CU_register_suites(suites);

    如果在註冊任何套件或測試用例期間發生錯誤,則返回錯誤碼。錯誤碼和普通的套件註冊和添加測試用例操作返回的錯誤碼是一樣的。用戶希望在一條語句中註冊多個CU_SuiteInfo數組的情況下,可以使用CU_register_nsuites()函數:

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

    此函數接受可變數量的CU_SuiteInfo數組。第一個參數表示傳遞的數組的實際數量。

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 數據結構成員中。如果爲激活狀態該標誌爲CU_TRUE,否則爲CU_FALSE。這使用戶能夠動態地選擇要執行的部分測試用例集。注意,禁用一個套件或測試用例然後又指定執行它將產生框架錯誤。這些函數如果執行成功返回CUE_SUCCESS,如果相應的套件(或測試用例)爲空的則返回 CUE_NOSUITE (或CUE_NOTEST)。

4.6. 修改套件和測試用例的其他屬性

通常,套件和測試用例的屬性是在創建時就設置好的。在某些情況下,用戶希望可以通過操縱這些屬性動態地修改測試結構。爲此提供了以下函數,應該使用這些函數,而不是直接設置數據結構成員的值。所有這些函數如果執行成功返回CUE_SUCCESS,失敗返回指示的錯誤代碼。查找函數可以幫助用戶定位特定的套件和測試用例。

  • CU_ErrorCode CU_set_suite_name(CU_pSuite pSuite, const char *strNewName)
    CU_ErrorCode CU_set_test_name(CU_pTest pTest, const char *strNewName)

    這兩個函數用於更改已註冊套件和測試用例的名稱。當前的名稱保存在 pSuite->pName 和 pTest->pName 數據結構成員中。如果 pSuite 或 pTest 爲 NULL,將分別返回 CUE_NOSUITE 或 CUE_NOTEST。如果 strNewName 爲 NULL,則分別返回 CUE_NO_SUITENAME 或 CUE_NO_TESTNAME。

  • CU_ErrorCode CU_set_suite_initfunc(CU_pSuite pSuite, CU_InitializeFunc pNewInit)
    CU_ErrorCode CU_set_suite_cleanupfunc(CU_pSuite pSuite, CU_CleanupFunc pNewClean)

    這兩個函數用於更改已註冊套件的初始化函數和清理函數。當前的函數保存在 pSuite->pInitializeFunc 和 pSuite->pCleanupFunc 數據結構成員中。如果 pSuite 爲 NULL 則返回 CUE_NOSUITE。

  • CU_ErrorCode CU_set_test_func(CU_pTest pTest, CU_TestFunc pNewFunc)

    該函數用於更改已註冊測試用例的測試函數。當前測試函數保存在 pTest->pTestFunc 數據結構成員中。如果 pTest 或者 pNewFunc 爲NULL 則返回 CUE_NOTEST。

4.7. 查找單個套件和測試

在大多數情況下,用戶將通過 CU_add_suite() 和 CU_add_test() 返回的指針來引用已註冊的套件和測試用例。有時,用戶可能需要重新獲取套件和測試用例的引用。如果用戶擁有關於實體的一些信息(註冊名稱或順序),下面的函數將協助用戶完成此操作。在對套件或測試用例一無所知的情況下,用戶需要迭代內部數據結構來枚舉套件和測試用例。API不直接支持此功能。

  • 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)

    這些函數有助於查找在活動註冊表中已註冊的套件。前2個函數可以通過名稱或位置查找套件,找不到則返回NULL。位置是從1開始的索引,範圍爲[1 … CU_get_registry()->uiNumberOfSuites]。當註冊有重名的套件時,這可能很有幫助,在這種情況下,按名稱查找只能檢索到第一個符合該名稱的套件。後2個函數幫助用戶識別已註冊套件的位置。如果找不到套件,則返回0。此外,如果註冊表未初始化,所有這些函數都會將CUnit錯誤狀態設置爲CUE_NOREGISTRY。如果strName爲NULL,則設置爲CUE_NO_SUITENAME,如果pSuite爲NULL,則設置爲CUE_NOSUITE。

  • 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)

    這些函數有助於查找在套件中已註冊的測試用例。前2個函數可以通過名稱或位置查找測試用例,找不到則返回NULL。位置是從1開始的索引,範圍爲[1 … pSuite->uiNumberOfSuites]。當註冊有重名的測試用例時,這可能很有幫助,在這種情況下,按名稱查找只能檢索到第一個符合該名稱的測試用例。後2個函數幫助用戶識別測試用例在套件中的位置。如果找不到測試用例,則返回0。此外,如果註冊表未初始化,所有這些函數都會將CUnit錯誤狀態設置爲CUE_NOREGISTRY。如果pSuite爲NULL,則設置爲CUE_NOSUITE,如果strName爲NULL,則設置爲CUE_NO_TESTNAME,如果pTest爲NULL,則設置爲CUE_NOTEST。

4.8. 已棄用的V1版本的數據類型和函數

從版本2開始,不推薦使用以下數據類型和函數。要使用這些已棄用的名稱,用戶代碼必須使用USE_DEPRECATED_CUNIT_NAMES宏定義進行編譯。

#include <CUnit/TestDB.h> (included automatically by CUnit/CUnit.h>).

棄用名稱 等效新名稱
TestFunc CU_TestFunc
InitializeFunc CU_InitializeFunc
CleanupFunc CU_CleanupFunc
_TestCase CU_Test
PTestCase CU_pTest
_TestGroup CU_Suite
PTestGroup CU_pSuite
add_test_group() CU_add_suite()
add_test_case() CU_add_test()
ADD_TEST_TO_GROUP() CU_ADD_TEST()
test_case_t CU_TestInfo
test_group_t CU_SuiteInfo
test_suite_t no equivalent - use CU_SuiteInfo
TEST_CASE_NULL CU_TEST_INFO_NULL
TEST_GROUP_NULL CU_SUITE_INFO_NULL
test_group_register CU_register_suites()
test_suite_register no equivalent - use CU_register_suites()

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> (included automatically by <CUnit/CUnit.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支持運行所有已註冊套件中的所有測試用例,同時也可以單獨運行套件和測試用例。在每次運行期間,框架會跟蹤記錄執行的套件、測試用例和執行通過或失敗的斷言數。注意,每次啓動測試運行時(即使失敗)都會清除先前的結果。如果用戶端希望將單個套件或測試用例排除在特定的測試運行之外,則可以禁用它們。但是,禁用一個套件或測試用例然後又明確請求執行它將產生框架錯誤。

雖然CUnit爲運行套件和測試用例提供了基本函數,但大多數用戶都希望使用簡化的用戶接口。這些接口處理與框架交互的細節,併爲用戶提供測試詳細信息和結果的輸出。

CUnit庫中包含以下接口:

Interface Platform Description
Automated all 非交互式,輸出到xml文件
Basic all 非交互式,可選輸出到stdout
Console all 交互式,控制檯模式
Curses Linux/Unix 交互式,curses模式

如果這些接口無法滿足需要,用戶還可以使用<CUnit/TestRun.h>中定義的原始框架API函數。有關如何直接與原語API交互的示例,請參見各種接口的源代碼。

5.3. 自動模式

自動模式的接口是非交互式的。用戶啓動測試運行,結果輸出到XML文件。已註冊的套件和測試用例的列表也可以輸出到XML文件中。

自動接口API由以下函數組成:

  • void CU_automated_run_tests(void)

    運行所有已註冊的(且爲激活的)套件中的所有測試用例。測試結果輸出到一個文件名爲ROOT-Results.xml.的文件中。可以使用CU_set_output_filename()設置文件名ROOT,或者使用默認的CUnitAutomated-Results.xml。注意,如果在每次運行之前沒有設置一個有別於ROOT的文件名,則結果文件將被覆蓋。

    結果文件支持兩種類型,文件類型定義文件(CUnit Run.dtd)和XSL樣式表(CUnit Run.xsl)。這些文件在源碼和安裝路徑的Share子目錄中都有提供。

  • CU_ErrorCode CU_list_tests_to_file(void)

    列舉出已註冊套件及相關測試用例到文件中。列表文件名爲ROOT-Listing.xml。可以使用CU_set_output_filename()設置文件名ROOT,或者使用默認的CUnitAutomated-Listing.xml。注意,如果在每次運行之前沒有設置一個有別於ROOT的文件名,則列表文件將被覆蓋。

    列表文件支持兩種類型,文件類型定義文件(CUnit Run.dtd)和XSL樣式表(CUnit Run.xsl)。這些文件在源碼和安裝路徑的Share子目錄中都有提供。

    還要注意的是,列表文件不是由CU_automated_run_tests()自動生成的。當用戶需要列表信息時,用戶代碼必須調用該接口作出明確的請求。

  • void CU_set_output_filename(const char* szFilenameRoot)

    Sets the output filenames for the results and listing files. szFilenameRoot is used to construct the filenames by appending -Results.xml and -Listing.xml, respectively.

    設置結果和列表文件的輸出文件名。通過szFilenameRoot並分別附加-Results.xml和-Listing.xml來構成文件名。

5.4. 基本模式

基本模式的接口也是非交互式的,結果輸出到標準輸出(stdout)。此接口支持運行單獨的套件或測試用例,並允許用戶代碼控制每次運行期間顯示的輸出類型。此接口爲希望簡化使用CUnit API的用戶提供了最大的靈活性。

提供以下公共函數:

  • CU_ErrorCode CU_basic_run_tests(void)

    運行所有已註冊套件中的所有測試用例。僅執行激活的套件,如果遇到非激活的套件,不會將其視爲錯誤,而是跳過。返回測試運行期間發生的第一個錯誤碼。輸出類型由當前的運行模式控制,該模式可以使用CU_basic_set_mode()進行設置。

  • CU_ErrorCode CU_basic_run_suite(CU_pSuite pSuite)

    運行指定單一套件中的所有測試用例。返回測試運行期間發生的第一個錯誤碼。如果pSuite爲NULL,則返回CUE_NOSUITE,如果pSuite未激活,則返回CUE_SUITE_INACTIVE。輸出類型由當前的運行模式控制,該模式可以使用CU_basic_set_mode()進行設置。

  • CU_ErrorCode CU_basic_run_test(CU_pSuite pSuite, CU_pTest pTest)

    運行指定套件中的一個測試用例。返回測試運行期間發生的第一個錯誤碼。如果pSuite爲NULL,則返回CUE_NOSUITE,如果pTest爲NULL,則返回CUE_NOTEST,如果pSuite未激活,則返回CUE_SUITE_INACTIVE,如果pTest不是套件中已註冊的測試用例,則返回CUE_TEST_NOT_IN_SUITE,如果pTest未激活,則返回CUE_TEST_INACTIVE。輸出類型由當前的運行模式控制,該模式可以使用CU_basic_set_mode()進行設置。

  • void CU_basic_set_mode(CU_BasicRunMode mode)

    設置運行模式,該模式在測試運行期間控制輸出。選擇如下:

    模式 描述
    CU_BRM_NORMAL 打印故障和運行結果
    CU_BRM_SILENT 除錯誤消息外不打印輸出
    CU_BRM_VERBOSE 最大程度地輸出運行的詳細信息
  • CU_BasicRunMode CU_basic_get_mode(void)

    獲取當前的運行模式。

  • void CU_basic_show_failures(CU_pFailureRecord pFailure)

    將所有失敗彙總打印到stdout。不依賴於運行模式。

5.5. 交互式控制檯模式

控制檯模式的接口是交互式的。用戶需要做的就是啓動控制檯會話,並且用戶以交互方式控制測試運行。這些操作包括選擇和運行套件和測試用例,以及查看測試結果。要啓動控制檯會話,使用:

  • void CU_console_run_tests(void)

5.6. 交互式curses模式

curses模式的接口是交互式的。用戶需要做的就是啓動控制檯會話,並且用戶以交互方式控制測試運行。這些操作包括選擇和運行套件和測試用例,以及查看測試結果。使用此接口需要將ncurses庫鏈接到應用程序中。要啓動控制檯會話,使用:

  • void CU_curses_run_tests(void)

5.7. 修改常規運行時行爲

以下函數允許用戶在測試運行期間修改框架的行爲:

  • void CU_set_fail_on_inactive(CU_BOOL new_inactive)
    CU_BOOL CU_get_fail_on_inactive(void)

    默認運行時行爲是,將非激活的套件和測試用例報告爲失敗。這樣客戶就能知道他們的測試結構已經部分被停用。如果用戶希望忽略非激活套件和測試用例,可以使用這些函數修改行爲。CU_FALSE表示框架將忽略非激活實體; CU_TRUE會將它們視爲失敗。

5.8. 獲得測試結果

以上接口都會呈現測試運行結果,但有時用戶代碼需要直接訪問結果。這些結果包括各種運行計數,以及保存故障詳細信息的故障記錄鏈接列表。注意,每次啓動新的測試運行或初始化或清理註冊表時,都會覆蓋測試結果。

訪問測試結果的函數如下:

  • 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)

    這些函數報告在上一次運行期間已運行或失敗的套件、測試用例和斷言的數量。如果套件的初始化或清理函數返回非NULL,或者套件處於非激活狀態並且框架設置爲將非激活套件/測試用例視爲失敗,則該套件被認爲失敗。一個測試用例中任何一個斷言失敗,或者在相同條件下處於非激活狀態,則該測試用例被認定爲失敗。最後3個函數返回的是對應情況的斷言數目。非激活套件(或測試用例)不計入已運行套件(測試用例)數量中。這樣做的結果是,套件(或測試用例)即使沒有報告已經運行也會失敗。

    要獲取已註冊套件和測試用例的總數,請分別使用CU_get_registry()−>uiNumberOfSuites和CU_get_registry()−>uiNumberOfTests。

  • const CU_pRunSummary CU_get_run_summary(void)

    一次性獲取所有測試結果計數。返回值是一個指向包含計數結果值的存儲結構的指針。此數據類型在<CUnit/TestRun.h>中定義(由<CUnit/CUnit.h>自動包含):

    typedef struct CU_RunSummary
    {
      unsigned int nSuitesRun;
      unsigned int nSuitesFailed;
      unsigned int nTestsRun;
      unsigned int nTestsFailed;
      unsigned int nAsserts;
      unsigned int nAssertsFailed;
      unsigned int nFailureRecords;
    } CU_RunSummary;
    
    typedef CU_Runsummary* CU_pRunSummary;
    

    返回的指針的結構變量爲框架所擁有,因此用戶不應該釋放或以其他方式更改它。注意,一旦啓動另一個測試運行,指針可能失效。

  • const CU_pFailureRecord CU_get_failure_list(void)

    獲取一個鏈表,該鏈表記錄着最後一次測試運行期間發生的所有失敗(NULL代表沒有失敗)。返回值的數據類型在<CUnit/TestRun.h> 中定義(由<CUnit/CUnit.h>自動包含)。每個失敗記錄都包含有關失敗位置和性質的信息:

    typedef struct CU_FailureRecord
    {
      unsigned int  uiLineNumber;
      char*         strFileName;
      char*         strCondition;
      CU_pTest      pTest;
      CU_pSuite     pSuite;
    
      struct CU_FailureRecord* pNext;
      struct CU_FailureRecord* pPrev;
    
    } CU_FailureRecord;
    
    typedef CU_FailureRecord*  CU_pFailureRecord;
    

    返回的指針的結構變量爲框架所擁有,因此用戶不應該釋放或以其他方式更改它。注意,一旦啓動另一個測試運行,指針可能失效。

  • unsigned int CU_get_number_of_failure_records(void)

    獲取由CU_get_failure_list()返回的失敗鏈表中CU_FailureRecord結點的個數。請注意,這個數值可能比失敗的斷言數量多,因爲套件的初始化和清除的失敗也計算在內。

5.9. 已棄用的V1版本的數據類型和函數

從版本2開始,不推薦使用以下數據類型和函數。要使用這些已棄用的名稱,用戶代碼必須使用USE_DEPRECATED_CUNIT_NAMES宏定義進行編譯。

棄用名稱 等效新名稱
automated_run_tests() CU_automated_run_tests() plus
CU_list_tests_to_file()
set_output_filename() CU_set_output_filename()
console_run_tests() CU_console_run_tests()
curses_run_tests() CU_curses_run_tests()

6. 錯誤處理

6.1. 摘要

#include <CUnit/CUError.h> (included automatically by <CUnit/CUnit.h>)

  typedef enum CU_ErrorCode
  CU_ErrorCode   CU_get_error(void);
  const char*    CU_get_error_msg(void);

  typedef enum CU_ErrorAction
  void           CU_set_error_action(CU_ErrorAction action);
  CU_ErrorAction CU_get_error_action(void);

6.2. CUnit錯誤處理

CUnit大多數函數設置錯誤代碼來表示的框架錯誤狀態。有些函數返回錯誤代碼,而有些函數只是設置錯誤代碼,並返回一些其他值。以下兩個函數用於檢查框架錯誤狀態:

CU_ErrorCode CU_get_error(void)
const char* CU_get_error_msg(void)

第一個函數返回錯誤代碼本身,而第二個函數返回描述錯誤狀態的消息。錯誤代碼是在<CUnit/CUError.h>文件中定義的一個CU_ErrorCode枚舉類型。

Error Value Description
CUE_SUCCESS 無錯誤狀態
CUE_NOMEMORY 內存申請失敗
CUE_NOREGISTRY 註冊表未初始化
CUE_REGISTRY_EXISTS 在沒有CU_cleanup_registry()前嘗試CU_set_registry()
CUE_NOSUITE 所需的CU_pSuite指針爲NULL
CUE_NO_SUITENAME 未提供所需CU_Suite名
CUE_SINIT_FAILED 套件初始化失敗
CUE_SCLEAN_FAILED 套件清除失敗
CUE_DUP_SUITE 不允許套件重名
CUE_NOTEST 所需的CU_pTest指針爲NULL
CUE_NO_TESTNAME 未提供所需的CU_Test名
CUE_DUP_TEST 不允許測試用例重名
CUE_TEST_NOT_IN_SUITE 測試用例未註冊在指定的套件中
CUE_TEST_INACTIVE 測試用例未激活
CUE_FOPEN_FAILED 打開一個文件時發生錯誤
CUE_FCLOSE_FAILED 關閉一個文件時發生錯誤
CUE_BAD_FILENAME 無效的文件名(NULL,空的,不存在等)
CUE_WRITE_ERROR 寫入文件時發生錯誤

6.3. 框架錯誤時的行爲

遇到一個錯誤條件時的默認行爲是設置錯誤代碼並繼續執行。在這種情況下,失敗的斷言不被認爲是“框架錯誤”。其他錯誤條件還包括套件初始化或者清理失敗,非激活套件或測試用例被顯示地執行,等等。有時,用戶可能更希望執行測試時停在框架發生錯誤的地方,甚至讓測試程序退出。這種行爲可以由用戶設置,提供以下函數:

  • void CU_set_error_action(CU_ErrorAction action)
    CU_ErrorAction CU_get_error_action(void)

    action是一個定義在<CUnit/CUError.h>文件中的CU_ErrorAction枚舉型變量。定義如下:

Error Value Description
CUEA_IGNORE 運行時發生錯誤,繼續運行(默認)
CUEA_FAIL 運行時發生錯誤,停止
CUEA_ABORT 運行時發生錯誤,退出應用程序

6.4. 已棄用的V1版本的數據類型和函數

從版本2開始,不推薦使用以下數據類型和函數。要使用這些已棄用的名稱,用戶代碼必須使用USE_DEPRECATED_CUNIT_NAMES宏定義進行編譯。

Deprecated Name Equivalent New Name
get_error() CU_get_error_msg()
error_code 沒用. 使用 CU_get_error()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章