簡體   English   中英

您如何使用 FFF 和 Google Test 在 C/C++ 中模擬和測試相同的 function?

[英]How do you mock and test the same function in C/C++ with FFF and Google Test?

我正在探索 TDD(測試驅動開發)來測試我在 C 和 C++ 中編寫的代碼。 我選擇使用 Google Test 作為單元測試框架。 我選擇使用 FFF 作為 mocking 框架。

我寫了一些測試並運行它們,效果很好。 但是我遇到了一個問題,我無法在網上找到任何參考資料,我希望社區可以幫助我(這也可以幫助其他人)。

我遇到的問題是我想為 function A1 編寫一個測試(參見下面的場景 1)。 由於它調用了另外三個函數(B1、B2 和 B3)並且它們有很多依賴關系,因此我決定模擬它們,以便測試可能影響 function A1 行為的各種場景。 為了使模擬工作並避免 linker 錯誤(例如“B1 的多個定義”),我需要在函數(B1、B2 和 B3)之前編寫“屬性((弱))”。 到目前為止,一切都很好。 一切都很好。

現在,考慮下面的場景 2。 在這種情況下,我想在單獨的測試中測試 function B1。 同樣,我也會模擬它調用的函數(C1、C2、C3)。 但是,問題是我不能調用“真實的”B1 function,因為如果我這樣做,我將得到我之前在 A1 測試中定義的模擬 function(場景 94F41A 下定義) ZC1C425268E68385D1AB5074C1

那么在這種情況下我該怎么辦呢? 謝謝。

模擬和測試相同的功能

在 James Grenning 所著的《嵌入式 C 的測試驅動開發》一書中,我做了一些更多的挖掘和發掘,至少有兩個解決這個問題的方法。

  1. 鏈接時替換
  2. Function 指針替換

鏈接時替換

此選項的一般摘要:
總體而言,這似乎不太直觀,難以實現和遵循,並且需要一些學習曲線。 但是,好處是您不需要更改生產代碼中的任何內容,這非常重要。

在這種情況下,您需要使用 makefile 來執行以下步驟:

  1. 將您的生產代碼構建到庫中

  2. 請務必將您的測試分成不同的文件,以便需要使用某個 function 作為模擬的測試與需要使用相同 function 的原始實現的測試分開。

  3. 使用 make 文件,您將需要對部分代碼進行微構建,最后將它們組合在一起。 例如,對於特定的 function,您希望在不同的測試中模擬和使用原始實現,這些測試被分隔在兩個文件中(test1.cpp 包含 func A1 的模擬實現,test2.cpp 包含function A1) 你會做:

  • 首先,將 test1.cpp 與生產代碼庫一起構建,並且不包含 test2.cpp。 模擬 function 將在鏈接期間優先。
  • 其次,將 test2.cpp 與生產代碼的庫一起構建,而無需 test1.cpp。 function A1 庫中的原始實現將在鏈接期間優先(因為它是那里唯一的實現)。
  • 將一起創建的兩個二進制文件組合成一個可執行文件。

現在這三個步驟只是一個高級解釋。 我知道這並不理想,但仍然值得。 我承認我不是自己做的,但我確實讀過 James Grenning 的書,如果您願意,他會在他的書中的附錄 1(標題為開發系統測試環境)中更詳細地解釋它,您可能會看到 makefile 結構他在這里使用了他的書代碼示例: https://pragprog.com/titles/jgade/test-driven-development-for-embedded-c/

Function 指針替換

此選項的一般摘要:
這更加直觀且易於實現。 但是,缺點是它需要對您的生產代碼進行細微的更改,在該代碼中聲明和定義了 function。

假設您要模擬一個名為 A1 的 function 文件 Production.c 文件中定義為:

//Production.c    
int A1(void)
{
  ... original implementation written here
} 

在 Production.h 中聲明為:

//Production.h    
int A1(void);

因此您可以通過這種方式更改 function 聲明和定義:

//Production.c    
int A1_original(void)
{
  ... original implementation written here
}

int (*A1)(void) = A1_original; 

在 Production.h 中聲明為:

//Production.h    
extern int (*A1)(void);

#ifdef TDD_ENABLED // use ifdef with TDD_ENABLED which is defined only in unit test project. This is because you want to declare the original implementation function as a public function so that you can freely assign it to the function pointer A1 in the test files.
    int A1_original(void);
#endif

現在,對於要使用原始 function 實現的每個測試,只需按照更改前的相同方式調用它:

A1();

如您所見,這意味着,在整個生產代碼中,您無需更改調用 function 的方式。 這也適用於您的測試文件。

現在,如果您想為此 function 使用模擬,您只需執行以下操作:

//Test1.cpp
int Fake_A1(void)
{
   ... fake function implementation
}

TEST(test_group_name,test_name)
{
    int (*temp_holder)(void) = A1;  // hold the original pointer in a temp pointer
    A1 = Fake_A1;      // assign A1 to call the mock function 

    ... run all the test here

   A1 = temp_holder; // assign A1 to call the original function back again so that the mock function is used only in the scope of this test
}

理想情況下,如果您打算進行多個此類測試,您可以在使用帶有Setup()和 Teardown( Teardown()的 class 並使用如下測試夾具 ( TEST_F ) 時對模擬進行分配並重新分配給原始 function:

//Test1.cpp
class A1_Func_Test : public ::testing::Test
{
protected:
    int (*temp_holder)(void) = A1;  // hold the original pointer in a temp pointer
    virtual void SetUp()
    {
        A1 = Fake_A1;  // assign A1 to call the mock function
    }

    virtual void TearDown()
    {
        A1 = temp_holder; // assign A1 to call the original function back again so that the mock function exists only in the scope of this test
    }
};

TEST_F(A1_Func_Test , Test1_A1)
{
    write tests here...
}

TEST_F(A1_Func_Test , Test2_A1)
{
    write tests here...
}

如何使用 FFF Mock 框架實現 function 指針替換:

按照上面編寫的說明進行操作后,應對您的生產代碼文件(Production.c 和 Production.h)進行相同的更改。 但是,對於您的單元測試文件,您只需執行以下操作,以防您想模擬 function (如果您想在沒有 mocking 的情況下測試 function )然后定期調用它:

//Test1.cpp
//Using FFF Mocking framework:

DEFINE_FFF_GLOBALS;
FAKE_VALUE_FUNC(int, A1_mock);

int A1_custom(void)
{
   write code here for mock function implementation...
}

TEST(test_group_name,test_name)
{
    int (*temp_holder)(void) = A1;  // hold the original pointer in a temp pointer
    // setting customized mock function for this test
    A1_mock_fake.custom_fake = A1_custom;
    A1 = A1_mock;

    // simple example of a test using the the FFF framework:
    int x;
    x = A1();
    ASSERT_EQ(A1_mock_fake.call_count, 1);

    // assign A1 to call the original function back again so that the mock function exists only in the scope of this test
    A1 = temp_holder;
    RESET_FAKE(A1_mock); // reset all parameters of the mock function used so when used in a subsequent test we will start "clean"
}

概括

我相信這回答了關於如何 go 的問題並按照我的要求進行操作。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM