前一篇介紹了如何在 Windows 上使用 Google Test 來 Unittest 既有的 Legacy C 語言程式碼,這一篇來教大家如何引入 fff 來加入不同類型的 Test Double(測試用假物件)。
Test Doubles
想要真正做到「單元」測試,就必須為其他人的程式碼作假,以避免其他人的程式碼干擾你想測試的部分。而這些暫時的替代品,就稱為 Test Double。其中又可分為 4 種用途:
- Dummy:只是用來 Build 過。
- Fake:可行的簡單實作。
- Stub:用來安插指定 Function 的返回值,以測試不同情況下的 Flow。
- Mock:用來檢查是否有正確使用外部 Function。
底下用一個簡單的例子説明:
extern "C" {
// here are the magic parts, will be explained in the following sections
#include "fff.h"
DEFINE_FFF_GLOBALS;
FAKE_VALUE_FUNC(int, get_hw_status);
FAKE_VOID_FUNC_VARARG(add_log, const unsigned char *, ...);
}
void some_hw_mux() {}; // dummy
int very_important_func()
{
int hw_status;
some_hw_mux();
hw_status = get_hw_status();
add_log("HW_STS %d", hw_status);
return (hw_status == 2) ? TRUE: FALSE;
}
TEST(TestFunc, Case1) {
int rtn;
get_hw_status_fake.return_val = 2; // stub
rtn = very_important_func();
ASSERT_EQ(rtn, TRUE);
ASSERT_EQ(add_log_fake.call_count, 1); // mock
}
其中,可以發現測試對象 very_important_func
的執行結果依賴 get_hw_status
,因此需要為它實現 Stub,以提前指定好返回值,來完成後面的 ASSERT_EQ
檢查。又我們想要檢查 very_important_func
有確實呼叫 add_log
,所以必須實現 Mock。另外雖然會呼叫到 some_hw_mux
,但因為它不影響整體行為,只需要為其加入 Dummy 即可。
雖然 Test Doubles 分為 4 種用途,但事務上並不需要強記各自差別,當你遇到想要繞掉的 Function 的時候,自然就會使用到相對應的假物件。
fff (Fake Function Framework)
雖然 Google Test 有內建 GMock,但那主要是針對 C++ 的 Class 做 Test Doubles,並不適合用於 C function。因此本篇選擇導入 fff (Fake Function Framework),只需要簡單引入一支 Header 檔就能開始使用,為我們的 C Function 實現各種 Test Doubles。
因為是要 Mock 掉 C Function,所以在 Test 的 .cc 檔案中一樣要寫在 extern "C" {}
block 裡面。加入這幾行:
extern "C" {
#include "fff.h"
DEFINE_FFF_GLOBALS;
// ... start your fake functions maker
}
Use Cases
如果想要 Mock 掉如下的宣告:
void some_hw_mux(void);
可以使用:
FAKE_VOID_FUNC(some_hw_mux);
如果後面有任何參數,都可以往後寫,例如:
void some_useful_func(int a, int b);
就寫成:
FAKE_VOID_FUNC(some_useful_func, int, int);
如果函式有返回值,例如:
int other_useful_adder(int a, int b);
可以使用:
FAKE_VALUE_FUNC(int, some_useful_adder, int, int);
如果最後一個參數是可變長度的參數 ...
,只要加上 _VARARG
後綴即可,例如:
void add_log(const unsigned char *, ...);
就寫成:
FAKE_VOID_FUNC_VARARG(add_log, const unsigned char *, ...);
How fff works?
像是 C 這樣的編譯語言,一包原始碼變成執行檔的過程,大致上可分為:預處理(Preprocesss)、編譯(Compilation)、組譯/彙編(Assemble)、鏈接(Linking)四大階段,而跟你無關的Function,例如那些待測對象 .c 檔的其他原始檔,在編譯的前面步驟中其實只會保留一個 Tag,用以在 Linking 時辨識要去連結哪一個真正的 Definition。fff 的作用,就是提供一個假實作,你寫的每一行 FAKE_
Define,都會展開成一個假 Definition,讓 Linker 去連結,以產生 Test Doubles。而產生出來的假物件,fff 允許你透過一個後綴 _fake
的結構體去管理。
所以你想要 Stub 一個假 Function 的返回值的話,你可以直接指定 return_value
:
get_hw_status_fake.return_val = 2;
又或者你想要提供 Fake 實作的話,可以指定 custom_fake
:
void my_alloc_buffer(void * buffer, int size)
{
buffer = malloc(size);
}
TEST(TestFunc, Case1) {
// ...
read_alloc_func_fake.custom_fake = my_alloc_buffer;
// ...
}
如果你想知道測試對象有沒有 Call 到 Mock,可以檢查 call_count
:
TEST(TestFunc, Case1) {
// ...
very_important_func();
EXPECT_EQ(add_log_fake.call_count, 1);
}
當然,也可以檢查帶入參數、歷史記錄等等,詳情請參閲 fff.h 的 README.md。
References