【單元測試】CMocka單元測試框架(支持mock功能)

1. 前言

本文內容涉及單元測試,需要讀者具有單元測試的基礎知識,如果沒有,請移步至我之前的博客文章:

2. CMocka概述

官網:https://cmocka.org/

CMocka 是一款支持 mock 對象、面向C語言的單元測試框架,CMocka 往往是編譯成庫的形式,供C單元測試程序鏈接調用。其前身是谷歌開發的 Cmockery,由於後者缺少維護,因此 CMocka 繼承了 Cmockery 並繼續維護。

CMocka 框架的特性:

  • 支持模擬對象,可設置模擬函數的期望返回值,期望輸出參數,可檢查模擬函數的輸入參數、函數調用順序。
  • 支持Test fixtures(包括setup和teardown).
  • 不依賴第三方庫,只需要一個C庫即可.
  • 支持衆多平臺(Linux, BSD, Solaris, Windows和嵌入式平臺)和編譯器(GCC, LLVM, MSVC, MinGW等).
  • 提供對異常信號(SIGSEGV, SIGILL, …)的處理。
  • 非fork()執行.
  • 提供基本的內存檢測,包括內存泄露,內存溢出檢測.
  • 提供豐富的斷言宏.
  • 支持多種格式輸出 (STDOUT, SUBUNIT, TAP, XML).
  • 開源.

3. CMocka編譯安裝

CMocka 往往是以庫的形式提供給測試程序鏈接調用的,爲此我們需要先將 CMocka 源碼編譯成庫。

從官網下載 CMocka 源碼:

git clone git://git.cryptomilk.org/projects/cmocka.git

編譯、安裝步驟在源碼中的 README.md 和 INSTALL.md 文件中有詳細說明,我的版本是 cmocka-1.1.5,在linxu系統下編譯安裝步驟爲:

# cd cmocka
# mkdir build && cd build
# cmake ..
# make
# make install

默認是安裝在 /usr/local 目錄下,安裝的文件不多,主要是頭文件和庫文件,就以下幾個:

Install the project...
-- Install configuration: ""
-- Installing: /usr/local/lib/pkgconfig/cmocka.pc
-- Installing: /usr/local/lib/cmake/cmocka/cmocka-config.cmake
-- Installing: /usr/local/lib/cmake/cmocka/cmocka-config-version.cmake
-- Installing: /usr/local/include/cmocka.h
-- Installing: /usr/local/include/cmocka_pbc.h
-- Installing: /usr/local/lib/libcmocka.so.0.7.0
-- Installing: /usr/local/lib/libcmocka.so.0
-- Installing: /usr/local/lib/libcmocka.so

也可以自定義安裝目錄,比如要安裝在 /usr 目錄下:

# cmake -DCMAKE_INSTALL_PREFIX=/usr ..
# make
# make install

更多的編譯選項,可以見 cmocka/INSTALL.md 文件說明。

4. 編寫CMocka測試程序

有了 CMocka 庫,就可以開始使用 CMocka 框架編寫單元測試程序了。

4.1. 通常用法

源碼 cmocka/example/simple_test.c 很好的展示了 CMocka 的通常用法,代碼如下所示:

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <stdint.h>
#include <cmocka.h>

/* A test case that does nothing and succeeds. */
static void null_test_success(void **state) {
    (void) state; /* unused */
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(null_test_success),
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}

其中 null_test_success 函數是測試用例,CMUnitTest 結構體是測試用例集(可以包含多個測試用例),每個測試用例可以設定可選的 startupteardown 函數,用於負責執行測試前的初始化和測試後的銷燬工作。上面示例中使用了 cmocka_unit_test 宏來填充 CMUnitTest 結構體中的測試用例( startupteardown 爲 NULL)。cmocka_run_group_tests 函數用於啓動測試並展示測試結果,可以爲測試集指定全局的 startupteardown(示例中都是NULL)。

編譯很簡單:

# cd cmocka/example
# gcc -o simple_test simple_test.c -lcmocka

其中 -lcmocka 會鏈接前面安裝的 /usr/local/lib/libcmocka.so 庫。執行測試程序看看效果:

# ./simple_test 
[==========] Running 1 test(s).
[ RUN      ] null_test_success
[       OK ] null_test_success
[==========] 1 test(s) run.
[  PASSED  ] 1 test(s).

如果提示找不到 cmocka 動態庫,可以先執行以下命令,讓新添加的 /usr/local/lib/libcmocka.so 動態庫能爲系統所識別:

# ldconfig

或者,將 /usr/local/lib/ 目錄加入 LD_LIBRARY_PATH 環境變量:

# export LD_LIBRARY_PATH=/usr/local/lib/:$LD_LIBRARY_PATH

4.2. 頭文件

#include <cmocka.h> 之前,必須先 #include 以下四個頭文件,這是官網 The CMocka API 中明確要求的,在 cmocka.h 頭文件開頭部分也有註明。

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <stdint.h>

4.3. 測試函數

CMocka 的測試用例是一個C函數,其函數簽名爲:

void test_func(void **state)

其中 void **state 指針是指向 CMUnitTest 結構體中的 void *initial_state 變量的地址,在初始化 CMUnitTest 結構體時會設置 initial_state 變量初始值(如 cmocka_unit_test 宏會設置 initial_state 爲 NULL,還有其他初始化的宏,會在後續中介紹),state 指針也會傳遞給測試用例對應的 setupteardown 函數。

int setup(void **state)
int teardown(void **state)

執行測試用例的過程中,會按先後順序依次調用 setuptest_functeardown 三個函數,它們都可以訪問(讀或寫) CMUnitTest 結構體中的 initial_state 變量。

源碼 cmocka/tests/test_basics.c 中的示例很好的演示了 state 指針的使用,代碼如下(有刪減):

static int setup(void **state) {
    int *answer = malloc(sizeof(int));
    assert_non_null(answer);
    *answer = 42;
    *state = answer;
    return 0;
}

static int teardown(void **state) {
    free(*state);
    return 0;
}

static void int_test_success(void **state) {
    int *answer = *state;
    assert_int_equal(*answer, 42);
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test_setup_teardown(int_test_success, setup, teardown),
    };
    return cmocka_run_group_tests(tests, NULL, NULL);
}

除此之外,源碼 cmocka/tests/test_fixtures.c 還演示了有關 state 指針的更多用法,可自行查閱。

4.4. 初始化CMUnitTest結構體

CMUnitTest 結構體變量用於存放測試用例集,該結構體定義如下:

struct CMUnitTest {
    const char *name;                /* 測試用例名 */
    CMUnitTestFunction test_func;    /* 測試用例函數指針 */
    CMFixtureFunction setup_func;    /* 測試用例對應的setup函數指針 */
    CMFixtureFunction teardown_func; /* 測試用例對應的teardown函數指針 */
    void *initial_state;             /* 測試用例私有數據指針 */
};

CMocka 提供了足夠豐富的宏用於初始化 CMUnitTest 結構體,官網API手冊中的 Running Tests 模塊列出了所有的這些宏定義:

#define 	cmocka_unit_test(f)   { #f, f, NULL, NULL, NULL }
#define 	cmocka_unit_test_setup(f, setup)   { #f, f, setup, NULL, NULL }
#define 	cmocka_unit_test_teardown(f, teardown)   { #f, f, NULL, teardown, NULL }
#define 	cmocka_unit_test_setup_teardown(f, setup, teardown)   { #f, f, setup, teardown, NULL }
#define 	cmocka_unit_test_prestate(f, state)   { #f, f, NULL, NULL, state }
#define 	cmocka_unit_test_prestate_setup_teardown(f, setup, teardown, state)   { #f, f, setup, teardown, state }

其中 cmocka_unit_test 宏最爲簡單,只設置測試用例函數,其他爲NULL。cmocka_unit_test_prestate_setup_teardown 宏最豐富,可設置測試用例的所有信息。

4.5. 執行測試

CMocka 即支持 cmocka_run_group_tests 函數執行所有測試用例集,也支持 run_test 函數執行單個測試用例。

static void null_test_success(void **state) {
    (void) state;
}

int main(void) {
    return run_test(null_test_success);
}

不管是哪種方式執行測試,當執行測試用例遇到錯誤時,會立刻中斷並退出當前測試用例,測試程序將繼續執行下一測試用例。

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

  • void fail(void)

    立刻中斷當前測試用例的執行,將其標記爲失敗,並繼續執行下一個測試用例會(如果有的話)。

  • void fail_msg(const char *msg,…)

    跟fail()一樣,只是多了打印日誌信息。

  • void skip(void)

    立刻中斷當前測試用例的執行,將其標記爲跳過,並繼續執行下一個測試用例會(如果有的話)。

源碼 cmocka/tests/test_skip.c 演示了 skip() 的用法,可自行查閱,fail() 用法類似。

5. CMocka API

官網API手冊 The CMocka API 中有這麼一張圖,它清晰的描述了 CMocka API 由哪些子模塊構成。

在這裏插入圖片描述

5.1. 斷言

CMocka 提供了一組用於測試邏輯條件的斷言,其使用方法和標準C中的 assert 差不多。比如要測試 add 函數(兩個整形數之和),可以:

static void test_add(void **state) {
    (void) state;

    assert_int_equal(add(1, 2), 3);
}

官網API手冊中的 Assert Macros 模塊中列出了 CMocka 框架支持的所有斷言(從斷言的名字就能看出其用途):

Assert Macros Description
assert_true (scalar expression)
assert_false (scalar expression)
Assert that the given expression is true (or false).
assert_int_equal (int a, int b)
assert_int_not_equal (int a, int b)
Assert that the two given integers are (or not) equal.
assert_float_equal (float a, float b, float epsilon)
assert_float_not_equal (float a, float b, float epsilon)
Assert that the two given float are (or not) equal given an epsilon.
Examples:
assert_float_equal(0.5f, 1.f / 2.f, 0.000001f);
assert_float_not_equal(0.5, 0.499f, 0.000001f);
assert_non_null (void *pointer)
assert_null (void *pointer)
Assert that the given pointer is non-NULL (or NULL).
assert_ptr_equal (void *a, void *b)
assert_ptr_not_equal (void *a, void *b)
Assert that the two given pointers are (or not) equal.
assert_string_equal (const char *a, const char *b)
assert_string_not_equal (const char *a, const char *b)
Assert that the two given strings are (or not) equal.
assert_memory_equal (const void *a, const void *b, size_t size)
assert_memory_not_equal (const void *a, const void *b, size_t size)
Assert that the two given areas of memory are (or not) equal.
assert_in_range (LargestIntegralType value, LargestIntegralType minimum, LargestIntegralType maximum)
assert_not_in_range (LargestIntegralType value, LargestIntegralType minimum, LargestIntegralType maximum)
Assert that the specified value is (or not) within the range of [minimum, maximum].
assert_in_set (LargestIntegralType value, LargestIntegralType values[], size_t count)
assert_not_in_set (LargestIntegralType value, LargestIntegralType values[], size_t count)
Assert that the specified value is (or not) within a set.
assert_return_code (int rc, int error) Assert that the return_code is greater than or equal to 0.

5.2. 模擬函數

CMocka 框架帶有模擬函數(Mock Functions)的功能,可以爲模擬函數設置期望返回值,設置期望輸出參數,檢查輸入參數,檢查調用順序。

5.2.1. 模擬函數返回值

官方API接口列表:見官網API手冊中的 Mock Objects 模塊。

爲了簡化模擬函數的實現,CMocka 爲模擬函數提供了存儲返回值的功能。CMocka 框架內部維護着每個模擬函數所特有的「返回值隊列」,will_return() 將期望返回值 push 到相應隊列中,mock() 再從相應隊列 pop 出期望返回值。

  • will_return(function, value)

    該宏用於給模擬函數function設置期望返回值value,一般是在測試用例中調用該宏。

  • mock()

    該宏用於返回will_return預設的期望值,它只能在模擬函數中調用。mock() 會根據自身所在的當前函數名(即調用 mock() 的函數),去框架內部隊列中尋找匹配的函數名(即尋找will_return指定的函數名),然後返回對應的期望返回值。一句話就是:返回當前函數的期望返回值。所以, mock() 必須在模擬函數 function 中調用,才能返回期望返回值 value,在其他地方調用 mock() 將無法返回期望值。

需要注意的是 will_return()mock() 要成對出現,執行測試用例的過程中,如果發現 will_return()mock() 沒有成對出現,就會將測試用例標記爲失敗。

舉個例子:

int mock_function(void)
{
    return (int) mock();
}

static void test_function(void **state)
{
    will_return(mock_function, 42);
    assert_int_equal(mock_function(), 42);
}

從官網API手冊中的 Mock Objects 模塊中可以看出,除了 will_return() 之外,設置期望返回值的AIP接口家族有好幾個。

will_return(function, value)
will_return_count(function, value, count)
will_return_always(function, value)
will_return_maybe(function, value)

其實,這些接口最終都是以 will_return_count 作爲基礎,理解了 will_return_count,就能掌握所有接口。will_return_count 的第三個參數 count 表明應該通過 mock() 返回的次數,即應該執行 mock() 的次數,如果不匹配,就會將測試用例標記爲失敗。如果 count 設置爲-1,表明可以無數次執行 mock(),但至少得執行一次。如果 count 設置爲-2,表明可以無數次執行 mock(),甚至一次都不執行也可以。

  • will_return(function, value) 相當於 will_return_count(function, value, 1)
  • will_return_always(function, value) 相當於 will_return_count(function, value, -1)
  • will_return_maybe(function, value) 相當於 will_return_count(function, value, -2)

源碼 cmocka/tests/test_returns.ccmocka/tests/test_returns_fail.c 演示了有關 Mock Objects 的更多用法,可自行查閱。

5.2.2. 模擬函數輸出參數

will_return() 除了可以預先設定模擬函數的期望返回值之外,還可以用於預先設定模擬函數的輸出參數期望值。函數的返回值和輸出參數都可以統稱爲return,結合代碼解釋:

int mock_function(char **out)
{
    *out = (char *) mock();
    return (int) mock();
}

static void test_function(void **state)
{
    char *out = NULL;
    const char * const hel = "hello";

    will_return(mock_function, hel);
    will_return(mock_function, 42);

    assert_int_equal(mock_function(&out), 42);
    assert_ptr_equal(out, hel);
}

在測試用例函數 test_function 中,調用了兩次 will_return() 往框架內部隊列push了兩個期望值,在模擬函數 mock_function 中,相應地,調用兩次 mock() 從隊列中pop出期望值,一個用於模擬函數輸出參數,一個用於模擬函數返回值。

5.2.3. 檢查模擬函數輸入參數

官方API接口列表:見官網API手冊中的 Checking Parameters 模塊。

除了存儲模擬函數的期望返回值之外,CMocka 框架內部還可以存儲模擬函數的輸入參數的期望值,以便測試用例能夠檢查模擬函數的輸入參數的正確性。在 CMocka 框架內部,維護着每個模擬函數所特有的Key-Value數據結構隊列,用於存儲模擬函數的形參字符串(作爲Key)及其輸入參數期望值(作爲Value)。

預先設置模擬函數輸入參數期望值的宏有很多,以下統稱爲 expect_*() 宏。下文以 expect_value 爲例進行說明,其他 expect_*() 宏的工作原理是類似的。

  • expect_value(function, parameter, value)

    該宏用於設定模擬函數的參數期望值,一般是在測試用例中調用該宏。通過該宏可將模擬函數function的形參名parameter及其參數期望值value組合成Key-Value數據,並push進CMocka框架內部的隊列中。

  • check_expected(parameter)

    該宏用於驗證模擬函數輸入參數值是否符合期望值,它只能在模擬函數中調用。該宏首先在CMocka框架內部模擬函數function所屬的Key-Value隊列中尋找parameter,找不到會將測試用例標記爲失敗,找到了則從隊列中pop出數據提取期望值Value,並跟實際傳入模擬函數的參數值進行對比,相同則測試通過,不同則測試失敗(測試用例標記爲失敗)。

結合例子進行說明:

void mock_function(int a)
{
    check_expected(a);
}

static void test_check_parameter(void **state)
{
    expect_value(mock_function, a, 42);
    mock_function(42);
}

在測試用例 test_check_parameter 中,expect_value 宏爲模擬函數 mock_function 的形參 a 設定了參數期望值42,緊接着調用模擬函數 mock_function 進行測試。而在模擬函數 mock_function 中,使用 check_expected 宏對輸入參數 a 做了檢查,檢查結果有以下幾種情況:

  1. 在CMocka框架內部的隊列中,沒有找到模擬函數 mock_function 參數a的預設期望值,則將測試標記爲失敗。沒有調用 expect_*() 就是這種情況。

  2. 找到模擬函數 mock_function 參數 a 的預設期望值(例子中期望值爲42),那就跟拿輸入參數的實際值和期望值進行對比,相同則測試成功,不同則測試失敗。例子中測試用例調用 mock_function 模擬函數傳入是42,所以測試通過。

expect_value() 宏相關還有一個叫 expect_value_count 的宏:

expect_value_count (function, parameter, value, count)

該宏幹麼用的呢?就某個模擬函數的某個參數而言,執行 expect_*() 宏的次數和執行 check_expected() 宏的次數要一致,必須成對被執行,否則 CMocka 框架會將測試用例標記爲失敗。在上面的例子中,如果測試用例要多次執行模擬函數 mock_function(內部執行 check_expected() 宏),就要預先多次執行 expect_value() 宏,使用 expect_value_count() 宏可以簡化這個操作,其中 count 指定運行 check_expected() 的次數。如果 count 設置爲-1,表明可以無數次執行 check_expected(),但至少得執行一次。如果 count 設置爲-2,表明可以無數次執行 check_expected(),甚至一次都不執行也可以。

expect_value() 宏相關還有一個叫 expect_not_value 的宏:

expect_not_value (function, parameter, value)

該宏幹麼用的呢?expect_value() 宏用於期望參數等於某個值,而 expect_not_value 宏用於期望參數不等於某個值。

官網API手冊中的 Checking Parameters 模塊中列出了 expect_*() 宏家族所有接口,它們的工作原理跟 expect_value 是一樣的。

expect_*() Macros Description
expect_check (function, parameter, check_function, check_data) 用於自定義
expect_in_set (function, parameter, value_array[])
expect_in_set_count (function, parameter, value_array[], count)
expect_not_in_set (function, parameter, value_array[])
expect_not_in_set_count (function, parameter, value_array[], count)
驗證參數值(整形)是否在期望值數組value_array[]之內(或之外)
expect_in_range (function, parameter, minimum, maximum)
expect_in_range_count (function, parameter, minimum, maximum, count)
expect_not_in_range (function, parameter, minimum, maximum)
expect_not_in_range_count (function, parameter, minimum, maximum, count)
驗證參數值(整形)是否在期望值[minimum , maximum]範圍之內(或之外)
expect_value (function, parameter, value)
expect_value_count (function, parameter, value, count)
expect_not_value (function, parameter, value)
expect_not_value_count (function, parameter, value, count)
驗證參數值(整形)是否等於(或不等於)期望值value
expect_string (function, parameter, string)
expect_string_count (function, parameter, string, count)
expect_not_string (function, parameter, string)
expect_not_string_count (function, parameter, string, count)
驗證參數值(字符串)是否等於(或不等於)期望值string
expect_memory (function, parameter, memory, size)
expect_memory_count (function, parameter, memory, size, count)
expect_not_memory (function, parameter, memory, size)
expect_not_memory_count (function, parameter, memory, size, count)
驗證參數值(內存塊)是否等於(或不等於)期望值內存塊memory
expect_any (function, parameter)
expect_any_count (function, parameter, count)
expect_any_always (function, parameter)
不管參數值是什麼,都判定通過。
expect_any_always就是expect_any_count (function, parameter, -1)
check_expected (parameter)
check_expected_ptr (parameter)
check_expected用於檢查模擬函數的參數值是否跟期望值一致,check_expected_ptr用於檢查指針是否跟期望的一致。

5.2.4. 檢查模擬函數調用順序

官方API接口列表:見官網API手冊中的 Call Ordering 模塊。

對模擬函數的檢查,除了檢查模擬函數的輸入參數和返回值之外,有時候也會檢查模擬函數的調用順序。爲了簡化實現,CMocka 框架內部維護了一個先進先出(FIFO)的「期望調用函數名隊列」。

  • expect_function_call(function)

    該宏用於設定模擬函數的期望調用順序,一般是在測試用例中調用該宏。通過該宏可以往隊列中 push 期望調用的模擬函數名 function,多次調用該宏的順序代表了期望調用模擬函數的順序。

  • function_called()

    該宏用於驗證模擬函數調用順序是否符合期望順序,它只能在模擬函數中調用。該宏從隊列中 pop 出期望調用的模擬函數名,並與當前調用 function_called() 的函數名進行對比,不一致就將測試用例標記爲失敗。

static void mock_test_a_called(void)
{
    function_called();
}

static void mock_test_b_called(void)
{
    function_called();
}

static void test_does_succeed_for_expected(void **state)
{
    (void)state;
    expect_function_call(mock_test_a_called);
    expect_function_call(mock_test_b_called);

    mock_test_a_called();
    mock_test_b_called();
}

通常情況下 expect_function_call()function_called() 是成對出現的,否則會導致測試用例失敗。特殊情況可以通過 ignore_function_calls() 繞過該限制。ignore_function_calls() 用於忽略某個期望函數的調用。

static void test_ordering_does_ignore_calls(void **state)
{
(void)state;
    expect_function_call(mock_test_a_called);
    ignore_function_calls(mock_test_b_called);

    mock_test_a_called();
    mock_test_b_called();
    mock_test_b_called();
}

源碼 cmocka/tests/test_ordering.ccmocka/tests/test_ordering_fail.c 演示了有關 Call Ordering 的更多用法,可自行查閱。

5.3. 動態內存檢測

官方API接口列表:見官網API手冊中的 Dynamic Memory Allocation 模塊。

CMocka 框架提供了動態內存檢測功能,用於測試待測模塊的內存溢出(越界訪問)、內存泄漏(未釋放內存)問題。CMocka 框架會跟蹤所有使用 test_*() 接口分配的內存塊,每次使用 test_free() 釋放內存塊,都會檢查內存塊是否有內存溢出,一旦發現就會將測試用例標記爲失敗,並終止當前測試用例。當測試用例執行完成時,CMocka 框架內部會檢測是否有內存塊還未釋放(內存泄漏),一旦發現也會將測試用例標記爲失敗。

因此,你需要做的就是將待測模塊代碼中的 mallocrealloccallocfree 分別替換成CMocka框架提供的 test_malloctest_realloctest_calloctest_free 接口。可以使用以下代碼替換:

#ifdef UNIT_TESTING
#define malloc test_malloc
#define realloc test_realloc
#define calloc test_calloc
#define free test_free
#endif

編譯測試用例時(包括編譯待測模塊代碼),開啓 UNIT_TESTING 宏定義,動態內存分配和釋放就會使用 CMocka 框架的 test_*() 接口。編譯產品發佈版本是,關閉 UNIT_TESTING 宏定義,動態內存分配和釋放就會使用默認的接口。舉個例子:

#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include <stdint.h>
#include <cmocka.h>
#include <stdlib.h>

#define UNIT_TESTING

#ifdef UNIT_TESTING
#define malloc test_malloc
#define realloc test_realloc
#define calloc test_calloc
#define free test_free
#endif

void leak_memory(void **state) {
    int * const temporary = (int*)malloc(sizeof(int));
    *temporary = 0;
}

void buffer_overflow(void **state) {
    char * const memory = (char*)malloc(sizeof(int));
    memory[sizeof(int)] = '!';
    free(memory);
}

void buffer_underflow(void **state) {
    char * const memory = (char*)malloc(sizeof(int));
    memory[-1] = '!';
    free(memory);
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(leak_memory),
        cmocka_unit_test(buffer_overflow),
        cmocka_unit_test(buffer_underflow),
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}

5.4. 異常檢測

程序在執行過程中,如果遇到異常,系統會按默認方式處理系統,很經常的就是終止(殺死)進程,這種情況很難定位問題出在哪裏,無法快速定位哪些函數引起的異常。

CMocka 框架爲了解決該問題,在執行測試用例之前,CMocka 框架內部會先覆蓋默認的異常/信號處理器,在攔截到異常信號後,只是打印出錯誤信息並退出當前測試用例,不會終止進程。以關鍵字 signal 全字匹配搜索 cmocka/src/cmocka.c 源碼就能看出端倪。

舉個例子:

static void null_test_success(void **state) {
    char *ch = NULL;
    *ch = 'Y';
}

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(null_test_success),
    };

    return cmocka_run_group_tests(tests, NULL, NULL);
}

執行結果如下:

[==========] Running 1 test(s).
[ RUN      ] test_segfault
[  ERROR   ] --- Test failed with exception: Segmentation fault(11)
[  FAILED  ] test_segfault
[==========] 1 test(s) run.
[  PASSED  ] 0 test(s).
[  FAILED  ] 1 test(s), listed below:
[  FAILED  ] test_segfault

 1 FAILED TEST(S)

源碼 cmocka/tests/test_exception_handler.c 演示了有關異常檢測的更多用法,可自行查閱。

5.5. 輸出格式

CMocka框架支持多種格式輸出 (STDOUT, SUBUNIT, TAP, XML),默認是輸出到STDOUT,有兩種方式可以設置輸出格式。

  • 通過 cmocka_set_message_output 函數接口進行配置。

    enum cm_message_output {
        CM_OUTPUT_STDOUT,
        CM_OUTPUT_SUBUNIT,
        CM_OUTPUT_TAP,
        CM_OUTPUT_XML,
    };
    void cmocka_set_message_output(enum cm_message_output output)
    

    示例代碼:

    int main(void) {
        const struct CMUnitTest tests[] = {
            cmocka_unit_test(null_test_success),
        };
    
        cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
        return cmocka_run_group_tests(tests, NULL, NULL);
    }
    
  • 通過環境變量 CMOCKA_MESSAGE_OUTPUT 配置,環境變量可以設置爲STDOUT、SUBUIT、TAP或XML。

    export CMOCKA_MESSAGE_OUTPUT=XML
    

    注意:如果同時使用了以上兩種方式設置輸出格式,環境變量 CMOCKA_MESSAGE_OUTPUT 配置優先級更高。

    默認情況下,輸出格式設置成XML,輸出內容也是打印到標準錯誤輸出(stderr)。可以通過環境變量 CMOCKA_XML_FILE 將輸出內容重定向到文件中,如果有多個用戶組,可以將CMOCKA_XML_FILE設置爲:

    CMOCKA_XML_FILE=/path/cm_%g.xml
    

    此時%g將被測試的group_name替換,併爲每個用戶組創建一個文件,否則所有用戶組都將打印到同一文件中。注意:環境變量 CMOCKA_XML_FILE 只能針對XML格式,對其他格式不起作用。

5.6. 測試用例過濾器

CMocka 框架還提供了兩個測試用例過濾器,用於在開始執行測試之前,過濾掉不想運行的測試用例:

void cmocka_set_test_filter(const char *pattern); /* 只運行匹配的測試用例 */
void cmocka_set_skip_filter(const char *pattern); /* 不運行匹配的測試用例 */

pattern 參數支持兩個通配符,一個是 *,表示匹配零個或多個字符,另一個是 ?,表示匹配一個字符。舉個例子:

int main(void) {
    const struct CMUnitTest tests[] = {
        cmocka_unit_test(test_skip1),
        cmocka_unit_test(test_skip2),
        cmocka_unit_test(test_fail),
    };

    cmocka_set_test_filter("test_skip*");             /* 只運行匹配test_skip*的測試用例 */
    cmocka_set_skip_filter("test_skip2");             /* 不運行匹配test_skip2的測試用例 */

    return cmocka_run_group_tests(tests, NULL, NULL); /* 綜上,最後只有test_skip1會被執行 */
}

源碼 cmocka/tests/test_skip_filter.ccmocka/tests/test_wildcard.c 演示了 cmocka_set_test_filter()cmocka_set_skip_filter() 的用法,可自行查閱。

6. 參考資料

撰寫本文時用到的 CMocka 版本是 cmocka-1.1.5,參考了以下資料:

參考資料 說明
源碼 cmocka/doc/index.html 附帶的文檔手冊
源碼 cmocka/example/ 示例代碼
源碼 cmocka/tests/ 示例代碼
https://api.cmocka.org 在線官網手冊
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章