gmock可以用来stubbing C函数吗?

xzlaal3s  于 2023-01-20  发布在  其他
关注(0)|答案(7)|浏览(258)

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

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

我搜索了gmock,据我所知,gmock不提供stub简单C函数的功能,因此我想问一下gmock提供mock或stub func_1的功能吗?
如果没有,我怎么能在不改变源代码的情况下手动在测试代码中存根func_1呢?我正在使用google测试框架进行单元测试。
谢谢。

djp7away

djp7away1#

这是我对这个问题的另一个答案。在第一个答案之后的两年里,我开始明白GMock只是一个错误的模仿C函数的框架。在你有很多函数要模仿的情况下,我之前发布的答案实在是太麻烦了。原因是GMock使用 Object Seams 来用模仿代码替换生产代码。这依赖于多态类,而C语言中没有。
相反,要模拟C函数,你应该使用 Link Seams,它在链接时用模拟代码替换生产代码。有几个框架可以实现这个目的,但我最喜欢的是 Fake Function FrameworkFFF)。看看它,它比GMock简单得多。它在C++应用程序中也能很好地工作。
对于感兴趣的,这里是一个good article由迈克尔羽毛关于不同的接缝类型。

p8ekf7hl

p8ekf7hl2#

最近我发现自己也遇到了同样的情况。我不得不为用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函数定义了一个存根,这将调用mock方法,注意extern对mock对象的引用。

#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的测试文件中,我必须 globally 定义模拟对象,这样它既可以在您的测试中访问,也可以从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中问过这个问题,现在还在等待答案。除此之外,我对这个解决方案很满意。

**UPDATE:**全局对象的问题可以通过创建对象(例如,在测试夹具的构造器中),并仅在全局变量中存储指向该对象的指针来容易地补救。

然而,也请注意我对这个问题的替代答案,我刚刚张贴。

uttx8gqw

uttx8gqw3#

我一直在寻找一个解决方案,用googleMock模拟遗留的c-函数,而不改变现有的代码已经很长一段时间,最后几天我发现下面的文章真的很棒:https://www.codeproject.com/articles/1040972/using-googletest-and-googlemock-frameworks-for-emb
今天,我使用gmock编写了第一个c函数单元测试,并以raspberry Pi编程的bcm2835.c库(http://www.airspayce.com/mikem/bcm2835/)中的两个函数为例:下面是我的解决方案:我在Eclipse和Windows下使用gcc 4.8.3.,注意设置编译器选项-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;
};
  • 示例化模拟库函数 *
// instantiate mocked lib
std::unique_ptr<BCM2835Lib_MOCK> TestFixture::_bcm2835libMock;
  • 伪造lib函数以连接模拟和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)

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

gt0wga4j

gt0wga4j4#

您可以使用Cutie库来模仿C函数GoogleMock风格。
回购中有完整的样品,但只是尝一尝:

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

of1yzvn45#

在我进行单元测试的一个项目中有一个类似的案例,我的解决方案是创建两个make文件,一个用于生产,一个用于测试。
如果函数func_1()在头文件a. h中定义,并在a.cpp中实现,那么为了测试,您可以添加一个新的源文件a_testing. cpp,它将以存根的形式实现a. h中的所有函数。对于单元测试,只需编译并链接a_testing. cpp而不是a. cpp,测试代码将调用您的存根。
在a_testing. cpp中,您可以将调用转发到gmock对象,该对象将根据状态和参数照常设置期望和操作。
我知道它并不完美,但它可以工作并解决问题,而根本不需要更改产品代码或接口。

bybem2ql

bybem2ql6#

在每个UT中,我们尝试验证特定的行为。
你应该在很难/不可能(我们需要隔离我们的单元)/花费大量时间(运行时间..)来模拟特定行为时假装一些东西。
显式地使用一个'C'函数意味着这个函数是你的单元的一部分(因此你不应该模仿它..)。在这个答案中,我解释了测试这个方法的主动性(在编辑中..)。在我看来,你应该用参数调用func,这些参数会使func_1模拟你想要验证的行为。
GMock基于编译伪(宏),因此您不能这样做。要伪'C'方法,您必须使用不同的工具,如Typemock Isolator++
如果不想使用Isolator++,那么应该重构方法;将func更改为func(int a, <your pointer the function>),然后使用指针代替func_1
我在这个答案中的图表可能有助于决定处理你的情况的方式。

xmq68pz9

xmq68pz97#

这可能不完全适合您的情况,但是如果您发现自己编写的C代码需要存根(例如,您想存根一些I/O连接),您可以使用函数指针。
假设您有一个头文件和源文件,其中包含以下函数声明和定义:
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

现在,如果你想stub这个方法,你所要做的就是把这个方法转换成一个函数指针,你还需要调整.c文件,因为我们改变了函数,把它变成了一个函数指针。
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

您不必在其他地方调整函数调用,因为func_1将被简单地重定向到func_1_Impl
现在来存根这个方法:
在你的*_test.cc文件(或者你的测试文件)中,你可以创建一个与some_file.h接口相同的模拟类,然后用定义的模拟函数覆盖函数指针。
一些文件测试.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);
}

此测试应通过,表明模拟工作正常。您还可以检查如果更改EXPECT_CALL调用(例如set .Times(AtLeast(2))),测试是否会失败。
注意:您可能会看到调整后的测试通过了AtLeast(2),尽管这是错误的。您仍然应该在控制台中看到正确的错误消息。
我希望这对你和其他有类似问题的人有帮助!

相关问题