【问题标题】:How do I use google mock in C?如何在 C 中使用 google mock?
【发布时间】:2013-11-02 09:15:13
【问题描述】:

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

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

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

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

编辑

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

【问题讨论】:

  • 我很好奇 C 项目中的哪些内容无法使用 C++ 编译器进行编译。
  • @carygregory 有时很简单,例如 foobar * x = malloc(sizeof(foobar)); 而不是将其转换为 (foobar *)malloc(sizeof(foobar));
  • 当您将 C 代码移动到 C++ 时,您通常需要添加大量类型转换(例如,对于所有对 malloc 的调用以及您不能再将 void * 隐式转换为类型化指针的任何其他地方)。
  • @HeywoodFloyd 在enum 上使用++ 的人可能应该得到他们得到的一切。 ;-)
  • @CareyGregory,除此之外,谁说目标有一个 C++ 编译器。我们有 8051 和其他一些嵌入式架构。

标签: c++ c unit-testing gcc


【解决方案1】:

来自 Google Mock 常见问题解答:

我的代码调用了一个静态/全局函数。我可以嘲笑它吗?
可以,但您需要进行一些更改。

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

这个谷歌测试博客post 说得很好。看看吧。

【讨论】:

  • 那是 C++。不幸的是,非测试代码需要保持 C。
  • @Alex 这就是 GoogleMock 提供的功能!其他任何东西都需要适配器/包装器。
【解决方案2】:

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

解决方案是将foobar 声明为映射到foobarImpl 的弱别名。在生产代码中,您不实现 foobar(),而对于单元测试,您提供调用静态模拟对象的实现。

此解决方案是 GCC 特定的,但还有其他提供弱别名的编译器/链接器。

  • 将函数void foobar();重命名为void foobarImpl();
  • 给函数foobar添加一个属性,比如:void foobar() __attribute__((weak, alias("foobarImpl") ));
  • 如果您想要一个非弱别名,请使用预处理指令从属性中删除弱别名。

因此:

#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() 的调用链接到 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));

【讨论】:

  • 很高兴看到您找到了一种包装 C API 函数的可行方法。那种解决方案/解决方法是我想到的......
  • 我很难理解答案。如果您 1. 解释主机和目标的含义(原始/重命名的 C 函数,C++ 替换?),2. 更正未声明的 FooImpl 的使用,3. 说,为什么没有 extern "C",也许会有所帮助在任何地方都提到过,4. 解释一下,在哪里使用了提到的强别名 - 我只能发现一个弱别名的声明,并可以选择使其变得强(非弱)?
  • @yau 很酷,感谢您的评论。我将编辑答案,但如果您查看这个问题,这是关于交叉编译 C 代码的问题,因此目标/主机的措辞是该场景的标准名称。我可以将答案编辑为更普遍适用。 3.) 是标准要求的,但这是 GCC 特定的解决方案,gcc 不会抱怨缺少的 mangling 声明。 4. 没有强别名声明,强别名只是链接器在看到重复符号时的默认行为。我将编辑以在 A 中没有措辞强别名。
  • 这里是 gcc 上目标/主机的答案:stackoverflow.com/q/7088576/887836。我没有将extern "C" 放在任何地方,因为代码仍然是使用真正的 C 编译器编译的,我不想将 C 代码与 C++ 结构混为一谈,并使用预处理器再次删除它们。
【解决方案3】:

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

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

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

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

【讨论】:

  • 嗯,我们使用 google mock 并且不能选择使用其他框架是有原因的。建议替代方案总是受欢迎的,但不是每个人都有可能使用替代方案(想象一下已经有 200000 个使用 C++ gmock 的测试,你会将它们全部移植到 cpputest 吗?)。在发布替代方案时,请给出一个最小的工作示例来展示其好处。
  • 我完全理解这一点,这就是为什么我如此试探性地给出我的答案!由于该问题没有要求提供竞争示例,因此我没有深入挖掘我的 2014 年代码并创建一些示例,抱歉 - 我认为在没有实际替代方案的情况下提供一个替代方案就足够了!我非常警惕给出毫无根据的“答案”,其中提问者询问“框架 X”,而回答者说“你尝试过框架 Y 吗?”在这种情况下,考虑到微弱的优势,我认为至少值得一提,而不必太深入。
  • 如果答案对找到问题的任何人有所帮助,我很乐意投票。不是每个人都在寻找我过去寻找的相同答案。我不能再更改那段代码了。
  • 如果时间允许,我可能会添加一个示例。
【解决方案4】:

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

您可以使用Mimicc 轻松地为与 GoogleTest 兼容的 C 函数自动生成模拟。查找任何头文件声明您要模拟的函数,将它们“编译”为模拟实现目标文件,并将它们链接到您的测试二进制文件中,包括mock_fatal()mock_failure() 函数的定义,如User Guide 中所述,专门针对谷歌测试。您必须使用 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 设置期望值和返回值,如上面的命令行调用所示。包括对 Google 测试的 mock_fatal()mock_failure() 的实现。

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

【讨论】:

  • 老实说,任何对某人有帮助并且比我已有五年历史的解决方案更现代的东西都应该被视为有价值的贡献。让我阅读并检查并在我花费时间后给您投票。谢谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-22
  • 1970-01-01
  • 2017-04-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多