如何在C语言中使用Google Mock?

w1jd8yoj  于 2023-10-16  发布在  Go
关注(0)|答案(4)|浏览(197)

我正在维护一个用C编写的遗留项目,用C编译器运行它是不可行的。由于代码是交叉编译的,因此可以在主机环境中运行单元测试或类似测试。因此,也可以与C主机编译器接口,并使用google-test和google-mock。
google-mock的某些功能似乎非常适合用于调用真实的实现和设置调用期望的测试。
我希望能够在C代码中使用它们。我可以看到,使用google-mock而不使用vtables确实是可能的,但它需要模板。
有没有办法用google mock来模拟裸C函数?

编辑

我基本上不得不使用谷歌模拟,我认为,虽然其他人谁会读这个线程有更好的灵活性比我。

fnx2tebb

fnx2tebb1#

我找到了一种在google-mock中模拟裸C函数的方法。
解决方案是将foobar声明为Map到foobarImpl的弱别名。在生产代码中,您不实现foobar(),而对于单元测试,您提供了一个调用静态模拟对象的实现。
这个解决方案是特定于GCC的,但也有其他编译器/链接器提供弱别名。

  • 将函数void foobar();重命名为void foobarImpl();
  • 在函数foobar中添加一个属性,如下所示:void foobar() __attribute__((weak, alias("foobarImpl") ));
  • 如果你想有一个非弱别名,使用preproessor指令从属性中删除弱别名。

因此:

#pragma once
void foobar();

成为

// header.h
#pragma once

void foobar();    
void foobarImpl(); // real implementation

extern "C" {
#include "header.h"
}
// code.c
void foobarImpl() {
  /* do sth */
}
void foobar() __attribute__(( weak, alias ("foobarImpl") )); // declare foobar to be a weak alias of foobarImpl

这将告诉gnu链接器在没有名为foobar()的符号时将foobar()的调用链接到foobarImpl()
然后添加测试代码

struct FooInterface {
   virtual ~FooInterface() {}
   virtual void invokeFoo() const { }
};

class MockFoo : public FooInterface {
public:
  MOCK_CONST_METHOD0(invokeFoo, void());
}

struct RealFoo : public FooInterface {
   virtual ~RealFoo() {}
   virtual void invokeFoo() const { foobarImpl(); }
};

MockFoo mockFoo;
RealFoo realFoo;
void foobar() {
  mockFoo.invokeFoo();
}

如果这段代码被编译和链接,它将用mock调用替换foobar。如果你真想调用foobar(),你仍然可以添加一个默认调用。

ON_CALL(mockFoo, invokeFoo())
       .WillByDefault(Invoke(&realFoo,&RealFoo::invokeFoo));
cig3rfwq

cig3rfwq2#

Google Mock FAQ:

我的代码调用静态/全局函数。我能嘲笑它吗?

你可以,但你需要做一些改变。
一般来说,如果你发现自己需要模拟一个静态函数,这表明你的模块耦合得太紧了(灵活性、可重用性、可测试性等都比较差)。最好定义一个小接口,并通过该接口调用函数,这样就可以很容易地模仿它。这是一个有点工作开始,但通常支付本身很快。
谷歌测试博客post说得很好。看看吧

ecbunoof

ecbunoof3#

你的问题特别提到了Google Mock,但没有说明使用该框架的任何其他原因。另一个答案建议使用一个似乎不必要的侵入性的解决方案。
因此,我希望我可以提出一个替代的建议,它可以很好地工作,而不必使用弱别名等。
我使用CppUTest(https://cpputest.github.io/)进行单元测试,并在几个大型的主要C项目(一些C++)上成功地进行了模拟。嘲笑的工作,而不必诉诸任何托辞上述排序。
不幸的是,项目文档有点薄弱,一些更好的(如果有点敏捷教条)信息和例子在书中(也被视为PDF格式)“嵌入式C的测试驱动开发”- James W Greening(ISBN-13:电话:978-1-934356-62-3)

ux6nzvsh

ux6nzvsh4#

我知道这是一个超级老的线程,但我希望我可以让别人的生活更容易,如果和当他们遇到这个问题。
您可以使用Mimicc轻松地自动生成与GoogleTest兼容的C函数的模拟。找到任何声明了你想要模拟的函数的头文件,将它们“编译”成模拟实现对象文件,并将它们链接到你的测试二进制文件中,包括Google Test用户指南中专门描述的mock_fatal()mock_failure()函数的定义。您必须使用Mimicc API来与Mimicc生成的模拟(即它不使用GoogleMock的API来设置期望值等),但它们可以与GoogleMock生成的模拟轻松共存。
更具体地说,假设您有一个C头文件foo.h,它声明了一些您想要模拟的函数。举例来说:

/*!
 * @param[out] pBuf Destination buffer to read to
 * @param[in] sz Size of the read buffer
 */
int magic_read(char *pBuf, const size_t sz);

/*!
 * @param[in] pBuf Source buffer to write from
 * @param[in] sz Size of the write buffer
 */
int magic_write(const char *pBuf, const size_t sz);

您可以通过编译foo.h与用于编译附带的生产foo.c的所有相同CFLAGS来创建这些的模拟:

prompt$ mimicc -c foo.h -o mock.o --hout=foo-mock.h -DSOME_PREPROC=1 -I <your includes>

要在测试中使用它,请使用foo-mock.h中声明的API设置期望和返回,如上面的命令行调用所示。包括mock_fatal()mock_failure()的Google Test实现。

#include <gtest/gtest.h>
#include <memory>

std::unique_ptr<char []> mockErrStr(const char *pLocation, unsigned count, const char *pMsg)
{
    const char *pFmtStr = "mock assertion failure! location: '%s',"
                          " iteration: %d, message: %s";
    size_t n = snprintf(NULL, 0, pFmtStr, pLocation, count, pMsg);
    std::unique_ptr<char []> outStrBuf(new char[n+1]);
    snprintf(outStrBuf.get(), n+1, pFmtStr, pLocation, count, pMsg);
    return outStrBuf;
}

void mock_failure(const char *pLocation, unsigned count, const char *pMsg)
{
    ADD_FAILURE() << mockErrStr(pLocation, count, pMsg).get();
}

void mock_fatal(const char *pLocation, unsigned count, const char *pMsg)
{
    FAIL() << mockErrStr(pLocation, count, pMsg).get();
    exit(1);
}

TEST_F(MimiccPoC, test1)
{
    char mock_ret_data[] = "HELLO WORLD";
    MOCK_FUNCTIONS(foo).magic_read.expect(32);
    MOCK_FUNCTIONS(foo).magic_read.andReturn(
            1, mock_ret_data, sizeof(mock_ret_data));

    char testRetBuf[32];
    int testRes = magic_read(testRetBuf, sizeof(testRetBuf));
    ASSERT_EQ(1, testRes);
    ASSERT_STREQ(testRetBuf, "HELLO WORLD");
}

虽然这看起来很多,但一旦设置了管道,您就可以自动模拟任何C或C++代码,而无需实际编写或维护额外的模拟代码,您只需专注于测试。从长远来看会容易一些。

相关问题