簡體   English   中英

gmock 可以用於存根 C 函數嗎?

[英]Can gmock be used for stubbing C functions?

我是 gmock 的新手,所以我想知道如何存根簡單的 C function 在 function 中調用進行單元測試。

例子:

int func(int a)
{
  boolean find;
  // Some code
  find = func_1();
  return find;
}

我搜索了 gmock,據我了解,gmock 不提供存根簡單 C 函數的功能,因此我想問一下 gmock 是否提供模擬或存根func_1的功能?

如果不是,我如何在不更改源代碼的情況下在我的測試代碼中手動存根func_1 我正在使用谷歌測試框架進行單元測試。

謝謝。

我最近發現自己處於同樣的境地。 我必須為用 C 編寫的庫編寫單元測試,而這些庫又依賴於同樣用 C 編寫的其他庫。所以我想使用gmock模擬所有依賴項的函數調用。 讓我通過一個例子來解釋我的方法。

假設要測試的代碼(庫 A)從另一個庫lib_x_function()調用一個函數:

lib_a_function()
{
   ...
   retval = lib_x_function();
   ...
}

所以,我想模擬庫 X。因此我在文件lib_x_mock.h編寫了一個接口類和一個模擬類:

class LibXInterface {
public:
   virtual ~LibXInterface() {}
   virtual int lib_x_function() = 0;
}

class LibXMock : public LibXInterface {
public:
   virtual ~LibXMock() {}
   MOCK_METHOD0(lib_x_function, int());
}

此外,我創建了一個源文件(比如lib_x_mock.cc ),它定義了實際 C 函數的存根。 這將調用模擬方法。 請注意對模擬對象的extern引用。

#include lib_x.h
#include lib_x_mock.h
extern LibXMock LibXMockObj;    /* This is just a declaration! The actual
                                   mock obj must be defined globally in your
                                   test file. */

int lib_x_function()
{
    return LibXMockObj.lib_x_function();
}

現在,在測試庫 A 的測試文件中,我必須全局定義模擬對象,以便在您的測試和lib_x_mock.cc都可以訪問它。 這是 lib_a_tests.cc:

#include lib_x_mock.h

LibXMock LibXMockObj;  /* This is now the actual definition of the mock obj */

...
TEST_F(foo, bar)
{
   EXPECT_CALL(LibXMockObj, lib_x_function());
   ...
}

這種方法非常適合我,我有幾十個測試和幾個模擬庫。 但是,我有一些疑問,是否可以創建全局模擬對象 - 我在一個單獨的問題中提出了這個問題,但仍在等待答案。 除此之外,我對解決方案感到滿意。


更新:關於全局對象的問題可以很容易地解決,例如在測試裝置的構造函數中創建對象,並將指向該對象的指針存儲在全局變量中。

但是,還要注意我剛剛發布的這個問題的替代答案。

這是我對這個問題的另一個回答。 在第一個答案之后的兩年里,我開始明白 GMock 只是一個錯誤的模擬 C 函數的框架。 在您有很多功能要模擬的情況下,我之前發布的答案實在是太麻煩了。 原因是 GMock 使用Object Seams 將生產代碼替換為模擬代碼。 這依賴於 C 中不存在的多態類。

相反,要模擬 C 函數,您應該使用Link Seams ,它在鏈接時用模擬代碼替換生產代碼。 為此目的存在多種框架,但我最喜歡的是Fake Function Framework ( FFF )。 看看吧,比GMock簡單多了。 它在 C++ 應用程序中也能很好地工作。

對於感興趣的人,這是 Michael Feathers 撰寫的一篇關於不同接縫類型的好文章

我已經找了很長時間來尋找一種解決方案來使用 googleMock 模擬遺留 c 函數而不更改現有代碼,最后幾天我發現了以下非常棒的文章: https ://www.codeproject.com/articles/1040972/using-googletest -and-googlemock-frameworks-for-emb

今天,我使用 gmock 為 c 函數編寫了我的第一個單元測試,並以 bcm2835.c 庫( http://www.airspayce.com/mikem/bcm2835/ )中的兩個函數為例,用於樹莓派編程:這是我的解決方案:我正在使用 gcc 4.8.3。 在 Eclipse 和 Windows 下。 請注意設置編譯器選項 -std=gnu++11。

這是我要測試的功能

int inits(void);
void pinMode(uint8_t pin, uint8_t mode);

int inits(){
    return bcm2835_init();
}

void pinMode(uint8_t pin, uint8_t mode){
    bcm2835_gpio_fsel(pin, mode);
}

包含和定義使用 googleTest / googleMock 進行單元測試

// MOCKING C-Functions with GMOCK :)
#include <memory>
#include "gtest/gtest.h"
#include "gmock/gmock.h"
using namespace ::testing;
using ::testing::Return;

模擬 BCM2835Lib 函數

class BCM2835Lib_MOCK{
public:
    virtual ~BCM2835Lib_MOCK(){}

    // mock methods
    MOCK_METHOD0(bcm2835_init,int());
    MOCK_METHOD2(bcm2835_gpio_fsel,void(uint8_t,uint8_t));
};

創建測試夾具

class TestFixture: public ::testing::Test{
public:
    TestFixture(){
        _bcm2835libMock.reset(new ::testing::NiceMock<BCM2835Lib_MOCK>());
    }
    ~TestFixture(){
        _bcm2835libMock.reset();
    }
    virtual void SetUp(){}
    virtual void TearDown(){}

    // pointer for accessing mocked library
    static std::unique_ptr<BCM2835Lib_MOCK> _bcm2835libMock;
};

實例化模擬的 lib 函數

// instantiate mocked lib
std::unique_ptr<BCM2835Lib_MOCK> TestFixture::_bcm2835libMock;

將 Mocks 與 c 函數連接起來的假庫函數

// fake lib functions
int  bcm2835_init(){return TestFixture::_bcm2835libMock->bcm2835_init();}
void bcm2835_gpio_fsel(uint8_t pin, uint8_t mode){TestFixture::_bcm2835libMock->bcm2835_gpio_fsel(pin,mode);}

從 TestFixture 創建 BCM2835 的單元測試類

// create unit testing class for BCM2835 from TestFixture
class BCM2835LibUnitTest : public TestFixture{
public:
    BCM2835LibUnitTest(){
        // here you can put some initializations
    }
};

使用 googleTest 和 googleMock 編寫測試

TEST_F(BCM2835LibUnitTest,inits){
    EXPECT_CALL(*_bcm2835libMock,bcm2835_init()).Times(1).WillOnce(Return(1));

    EXPECT_EQ(1,inits()) << "init must return 1";
}

TEST_F(BCM2835LibUnitTest,pinModeTest){

    EXPECT_CALL(*_bcm2835libMock,bcm2835_gpio_fsel( (uint8_t)RPI_V2_GPIO_P1_18
                                                   ,(uint8_t)BCM2835_GPIO_FSEL_OUTP
                                                  )
               )
               .Times(1)
               ;

    pinMode((uint8_t)RPI_V2_GPIO_P1_18,(uint8_t)BCM2835_GPIO_FSEL_OUTP);
}

結果 :)

[----------] 2 tests from BCM2835LibUnitTest
[ RUN      ] BCM2835LibUnitTest.inits
[       OK ] BCM2835LibUnitTest.inits (0 ms)
[ RUN      ] BCM2835LibUnitTest.pinModeTest
[       OK ] BCM2835LibUnitTest.pinModeTest (0 ms)
[----------] 2 tests from BCM2835LibUnitTest (0 ms total)

希望它會有所幫助:) - 對我來說,這是一個真正有效的解決方案。

您可以使用Cutie庫來模擬 GoogleMock 風格的 C 函數。
回購中有一個完整的樣本,但只是一個味道:

INSTALL_MOCK(close);
CUTIE_EXPECT_CALL(fclose, _).WillOnce(Return(i));

在我進行單元測試的項目中,我有一個類似的案例。 我的解決方案是創建兩個 make 文件,一個用於生產,一個用於測試。

如果函數func_1()定義在頭文件ah中,並在a.cpp中實現,那么為了測試你可以添加一個新的源文件a_testing.cpp,它將把ah中的所有功能作為存根來實現。 對於單元測試,只需使用 a_testing.cpp 而不是 a.cpp 編譯和鏈接,經過測試的代碼將調用您的存根。

在 a_testing.cpp 中,您可以將調用轉發到 gmock 對象,該對象將根據狀態和參數照常設置期望和操作。

我知道它並不完美,但它可以在不更改生產代碼或接口的情況下解決問題。

在每個 UT 中,我們都試圖驗證特定的行為。

當它非常困難/不可能(我們需要隔離我們的單元)/花費大量時間(運行時間..)來模擬特定行為時,您應該偽造一些東西。

以顯式方式使用“C”函數意味着該函數是您的單元的一部分(因此您不應該嘲笑它......)。 這個答案中,我解釋了按原樣測試方法的主動性(在編輯中..)。 在我看來,您應該使用導致func_1模擬您要驗證的行為的參數調用func

GMock基於編譯 fake(macros),因此你不能做這樣的事情。 要偽造“C”方法,您必須使用不同的工具,例如Typemock Isolator++

如果您不想使用Isolator++ ,那么您應該重構您的方法; 變化funcfunc(int a, <your pointer the function>)然后使用指針而不是func_1

我在這個答案中的圖表可能有助於決定處理您案件的方式。

這可能不完全適合您的情況,但如果您發現自己正在編寫需要存根的 C 代碼(例如,您想要存根某些 I/O 連接),則可以使用 function 指針。

因此,假設您有一個 header 和具有以下 function 聲明和定義的源文件:

some_file.h

// BEGIN SOME_FILE_H
#ifndef SOME_FILE_H
#define SOME_FILE_H

#include <stdbool.h>

bool func_1(void);

#endif // SOME_FILE_H
// END SOME_FILE_H

some_file.c

// BEGIN SOME_FILE_C
#include "some_file.h"

bool func_1(void) {
  return true;
}
// END SOME_FILE_C

現在,如果你想存根這個方法,你所要做的就是把這個方法轉換成一個 function 指針。 您還必須調整 .c 文件,因為我們更改了 function 並將其設為 function 指針。

some_file.h

// BEGIN SOME_FILE_H
#ifndef SOME_FILE_H
#define SOME_FILE_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stdbool.h>

extern bool (*func_1)(void);

#ifdef __cplusplus
}
#endif

#endif // SOME_FILE_H
// END SOME_FILE_H

some_file.c

// BEGIN SOME_FILE_C
#include "some_file.h"

// Make the method static, so that it cannot be accessed anywhere else except in this file
static bool func_1_Impl(void) {
    return true;
}
bool (*func_1)(void) = func_1_Impl;
// END SOME_FILE_C

您不必在其他任何地方調整 function 調用,因為func_1將簡單地重定向到func_1_Impl

現在存根這個方法:

在您的*_test.cc文件(或任何您稱之為測試文件的文件)中,您可以創建一個模擬 class ,其接口與some_file.h相同。 然后,您可以使用定義的模擬 function 覆蓋 function 指針。

some_file_test.cc

#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include "some_file.h"
#include "header_where_func_is_declared.h"

using ::testing::AtLeast;

class SomeFile {
public:
    virtual bool func1() = 0;
};

class MockSomeFile : SomeFile {
public:
    MOCK_METHOD(bool, func1, (), (override));
};

TEST(Func1Test, ShouldMockStuff) {
    // Arrange
    auto expected = 0; // or whatever the expected result is

    // Define the mock object to be used
    static MockSomeFile mock;

    // The important part: Overwrite the function pointer (with a lambda function)
    func_1 = []() { return mock.func1(); };

    // Define the call expectations
    EXPECT_CALL(mock, func1)
            .Times(AtLeast(1));

    // Act
    auto actual = func();

    // Assert
    EXPECT_EQ(expected, actual);
}

該測試應該通過,表明 mocking 有效。 如果更改EXPECT_CALL調用,您還可以檢查測試是否會失敗,例如 set .Times(AtLeast(2))

注意:您可能會看到調整后的測試通過了AtLeast(2) ,盡管這是錯誤的。 您仍然應該在控制台中看到正確的錯誤消息。

我希望這可以幫助您和其他遇到類似問題的人!

暫無
暫無

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

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