文章目錄
1. 前言
本文內容涉及單元測試,需要讀者具有單元測試的基礎知識,如果沒有,請移步至我之前的博客文章:
2. CMocka概述
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
結構體是測試用例集(可以包含多個測試用例),每個測試用例可以設定可選的 startup
和 teardown
函數,用於負責執行測試前的初始化和測試後的銷燬工作。上面示例中使用了 cmocka_unit_test
宏來填充 CMUnitTest
結構體中的測試用例( startup
和 teardown
爲 NULL)。cmocka_run_group_tests
函數用於啓動測試並展示測試結果,可以爲測試集指定全局的 startup
和 teardown
(示例中都是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
指針也會傳遞給測試用例對應的 setup
和 teardown
函數。
int setup(void **state)
int teardown(void **state)
執行測試用例的過程中,會按先後順序依次調用 setup
、test_func
和 teardown
三個函數,它們都可以訪問(讀或寫) 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.c
和 cmocka/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
做了檢查,檢查結果有以下幾種情況:
-
在CMocka框架內部的隊列中,沒有找到模擬函數
mock_function
參數a的預設期望值,則將測試標記爲失敗。沒有調用expect_*()
就是這種情況。 -
找到模擬函數
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.c
和 cmocka/tests/test_ordering_fail.c
演示了有關 Call Ordering 的更多用法,可自行查閱。
5.3. 動態內存檢測
官方API接口列表:見官網API手冊中的 Dynamic Memory Allocation 模塊。
CMocka 框架提供了動態內存檢測功能,用於測試待測模塊的內存溢出(越界訪問)、內存泄漏(未釋放內存)問題。CMocka 框架會跟蹤所有使用 test_*()
接口分配的內存塊,每次使用 test_free()
釋放內存塊,都會檢查內存塊是否有內存溢出,一旦發現就會將測試用例標記爲失敗,並終止當前測試用例。當測試用例執行完成時,CMocka 框架內部會檢測是否有內存塊還未釋放(內存泄漏),一旦發現也會將測試用例標記爲失敗。
因此,你需要做的就是將待測模塊代碼中的 malloc
、realloc
、calloc
、free
分別替換成CMocka框架提供的 test_malloc
、test_realloc
、test_calloc
、test_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.c
和 cmocka/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 | 在線官網手冊 |