21

我正在维护一个用 C 编写的遗留项目,用 C++ 编译器运行它是不可行的。由于代码是交叉编译的,因此可以在主机环境中运行单元测试或类似的测试。因此,也可以与 C++ 主机编译器接口并使用 google-test 和 google-mock。

google-mock 的某些功能似乎很容易被用于测试,因为它可以调用真实的实现和设置调用期望。

我希望能够在 C 代码中使用它们。我可以看到确实可以在不使用 vtables 的情况下使用 google-mock,但它需要模板。

有没有办法用 google mock 模拟裸 C 函数?

编辑

我基本上不得不使用 google mock,但我认为阅读这个帖子的其他人都比我有更好的灵活性。

4

4 回答 4

15

我找到了一种能够在 google-mock 中模拟裸 C 函数的方法。

解决方案是声明foobar为映射到的弱别名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()with的调用foobarImpl()foobar()

然后添加测试代码

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

如果此代码已编译并链接,它将替换foobar为模拟调用。如果您真的想打电话foobar(),您仍然可以添加默认调用。

ON_CALL(mockFoo, invokeFoo())
       .WillByDefault(Invoke(&realFoo,&RealFoo::invokeFoo));
于 2013-10-24T07:34:31.577 回答
3

从谷歌模拟常见问题解答:

我的代码调用一个静态/全局函数。我可以嘲笑它吗?
你可以,但你需要做一些改变。

一般来说,如果您发现自己需要模拟一个静态函数,这表明您的模块耦合太紧密(并且灵活性较差、可重用性较差、可测试性较差等)。您最好定义一个小接口并通过该接口调用该函数,然后可以轻松地对其进行模拟。最初需要做一些工作,但通常会很快收回成本。

这篇 Google 测试博客文章说得非常好。看看这个。

于 2013-10-23T21:43:29.683 回答
2

您的问题特别提到了 Google Mock,但没有说明使用该框架的任何其他原因。另一个答案建议使用一种似乎不必要的侵入性的解决方法。

因此,我希望允许我提出一个效果很好的替代建议,而不必使用弱别名等。

我已经使用 CppUTest ( https://cpputest.github.io/ ) 进行模拟单元测试,在几个大型的主要 C 项目(一些 C++)上成功。嘲弄工作无需诉诸上述任何诡计。

不幸的是,项目文档有点薄弱,书中有一些更好的(如果有点敏捷教义)信息和示例(也可以看到以 PDF 的形式传播)“嵌入式 C 的测试驱动开发” - James W Greening(ISBN-13: 978-1-934356-62-3)

于 2016-11-21T11:41:22.563 回答
2

我意识到这是一个非常古老的线程,但我希望当他们遇到这个问题时,我可以让他们的生活更轻松一些。

您可以使用Mimicc轻松地为与 GoogleTest 兼容的 C 函数自动生成模拟。找到任何头文件声明您要模拟的函数,将它们“编译”成模拟实现目标文件,并将它们链接到您的测试二进制文件中,包括专门针对 Google 测试的用户指南中描述的mock_fatal()和函数的定义。您必须使用 Mimicc API 与 Mimicc 生成的模拟交互(即它不使用 GoogleMock 的 API 来设置期望等),但它们可以舒适地与 GoogleMock 生成的模拟一起使用。mock_failure()

更具体地说,假设您有一个 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的相同内容来为这些创建模拟:CFLAGSfoo.c

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

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

#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++ 代码,而无需实际编写或维护额外的模拟代码,您只需专注于测试。从长远来看,要容易一些。

于 2021-05-13T15:16:28.820 回答