【问题标题】:Can gmock be used for stubbing C functions?gmock 可以用于存根 C 函数吗?
【发布时间】:2015-08-13 13:02:27
【问题描述】:

我是 gmock 的新手,所以我想知道如何为单元测试的被测函数中调用的简单 C 函数存根。

例子:

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

我搜索了 gmock,据我了解 gmock 不提供存根简单 C 函数的功能,因此我想问一下 gmock 是否提供模拟或存根 func_1 的功能?

如果不是,我如何在我的测试代码中手动存根 func_1 而无需更改源代码?我正在使用 google 测试框架进行单元测试。

谢谢。

【问题讨论】:

  • func_1() 也是'C'函数吗?
  • @OldFox 是的,它是一个 C 函数。
  • func_1() 是提供了复杂的场景还是使用了无法测试的依赖项?(例如硬件)
  • 好吧,我只是举了一个“func_1()”的例子。它可以实现简单和复杂的场景。但是,就我而言,没有不可测试的依赖关系。

标签: c unit-testing googletest gmock


【解决方案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());
   ...
}

这种方法非常适合我,我有几十个测试和几个 嘲笑图书馆。但是,我有一些疑问是否可以创建一个 全局模拟对象 - 我在 separate question 中问过这个问题,但仍在等待答案。除此之外,我对解决方案很满意。


更新: 关于全局对象的问题可以通过创建对象来轻松解决,例如在测试夹具的构造函数中,并将指向该对象的指针存储在全局变量中。

但是,请注意我刚刚发布的对这个问题的替代答案。

【讨论】:

  • 我遵循了你的程序。唯一的区别是给定函数中没有参数。相比之下,我的函数中有两个参数(一个是 struct,另一个是 int)。在界面virtual DltReturnValue dlt_client_connect(DltClient *client, int verbose) = 0; 和测试文件TEST_F(DltLogClient_test, init) { DltClient *client = new DltClient(); EXPECT_CALL(MockXIFObj, dlt_client_init(client, 0)); EXPECT_EQ(0, dltLogclient->init()); } 中我得到一个SEGFAULT。我哪里错了?
  • 这会崩溃,因为模拟对象在测试完成后被销毁,但它应该作为测试的一部分被销毁。所以 matthandi 解决方案是正确的,而这个不是。
  • 如果这导致崩溃,那么它与您的代码有关。但是就像我在底部的编辑中写的那样,您可以轻松地让模拟对象与测试夹具一起构造和破坏。那应该可以解决您的问题。
【解决方案2】:

这是我对这个问题的另一个回答。在第一个答案之后的两年里,我开始明白 GMock 只是用于模拟 C 函数的错误框架。在您有很多要模拟的功能的情况下,我之前发布的答案太麻烦了。原因是 GMock 使用 Object Seams 将生产代码替换为模拟代码。这依赖于 C 中不存在的多态类。

相反,要模拟 C 函数,您应该使用 Link Seams,它在链接时将生产代码替换为模拟代码。为此目的存在多个框架,但我最喜欢的一个是 Fake Function Framework (FFF)。看看,它比 GMock 简单多了。它在 C++ 应用程序中也能很好地工作。

对于感兴趣的人,这里是 Michael Feathers 的 good article,关于不同的接缝类型。

【讨论】:

    【解决方案3】:

    我一直在寻找一种在不更改现有代码的情况下使用 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));
    };
    

    创建一个TestFixture

    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;
    };
    

    实例化模拟库函数

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

    将 Mocks 与 c 函数连接起来的假 lib 函数

    // 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)
    

    希望它会有所帮助 :) - 对我来说,这是一个真正有效的解决方案。

    【讨论】:

    • 您为“实例化模拟库函数”显示的代码在哪里?它在哪个 .cpp 文件中?
    【解决方案4】:

    您可以使用 Cutie 库来模拟 C 函数 GoogleMock 样式。
    回购中有一个完整的样本,但只是一个味道:

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

    【讨论】:

    • 我们如何将 Cutie 安装为独立库。其实我有Automake项目,我想把Cutie集成进去。
    • 实际上,将 Cutie 用作独立库没有直接的选择。我想我应该为那些不使用 CMake 的人实现它。
    • 这将是一个很大的帮助。 :)
    【解决方案5】:

    我在进行单元测试的项目中有一个类似的案例。我的解决方案是创建两个 make 文件,一个用于生产,一个用于测试。

    如果函数 func_1() 在头文件 a.h 中定义,并在 a.cpp 中实现,那么为了测试,您可以添加一个新的源文件 a_testing.cpp,它将实现 a.h 中的所有函数作为存根。 对于单元测试,只需编译和链接 a_testing.cpp 而不是 a.cpp,测试代码将调用您的存根。

    然后,您可以在 a_testing.cpp 中将调用转发到 gmock 对象,该对象将根据状态和参数像往常一样设置期望和操作。

    我知道它并不完美,但它可以在完全不更改生产代码或接口的情况下解决问题。

    【讨论】:

      【解决方案6】:

      在每个 UT 中,我们都在尝试验证特定行为。

      当非常困难/不可能(我们需要隔离我们的单位)/花费大量时间(运行时间..)来模拟特定行为时,您应该伪造一些东西。

      以显式方式使用“C”函数意味着该函数是您单元的一部分(因此您不应该模拟它..)。在this 回答中,我解释了按原样测试方法的倡议(在编辑中..)。在我看来,您应该使用导致func_1 模拟您要验证的行为的参数调用func

      GMock 是基于编译假(宏),因此你不能做这样的事情。要伪造“C”方法,您必须使用不同的工具,例如 Typemock Isolator++

      如果你不想使用Isolator++,那么你应该重构你的方法;将func 更改为func(int a, &lt;your pointer the function&gt;),然后使用指针代替func_1

      this 答案中的图表可能有助于决定处理您的案件的方式。

      【讨论】:

      • 您的图表很好地说明了单元测试需要投入多少精力。但我有一个问题。如果func_1 指向许多依赖项(函数)然后在实现func_1 时进行测试,即集成测试可能非常耗时。如果源代码func 无法更改怎么办。有没有办法告诉编译器指向一个存根函数而不是在不更改源代码的情况下实现真正的func_1
      • @user3159610 没有办法告诉编译器存根方法。从您的最后评论看来,您的方法是遗留代码。使用像isolator++ 这样的代码编织工具的主要原因是遗留代码。如果它是遗留代码,那么我会建议您跳过测试或仅为主要流程创建集成测试。我同意你关于集成测试(耗时)的观点,但是如果代码值得 UT,你应该在 sprint 中获得重构任务或工具的钱......
      猜你喜欢
      • 2021-09-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-09
      • 1970-01-01
      • 2020-04-19
      相关资源
      最近更新 更多