繁体   English   中英

我如何在 C 中使用谷歌模拟?

[英]How do I use google mock in C?

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

google-mock 的某些功能似乎非常适合用于测试,例如调用实际实现和设置调用期望。

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

有没有办法用谷歌模拟来模拟裸 C 函数?

编辑

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

我找到了一种能够在 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()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));

来自 Google Mock 常见问题解答:

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

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

这篇 Google 测试博客文章说得很好。 一探究竟。

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

因此,我希望我可以提出一个可以很好地工作的替代建议,而不必使用弱别名等。

我已经使用 CppUTest ( https://cpputest.github.io/ ) 进行单元测试和模拟,成功地在几个大型的 C 项目(一些 C++)上成功。 无需借助上述任何诡计即可进行嘲讽。

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

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

您可以非常轻松地使用Mimicc为与 GoogleTest 兼容的 C 函数自动生成模拟 找到任何声明您要模拟的函数的头文件,将它们“编译”为模拟实现对象文件,并将它们链接到您的测试二进制文件中,包括专门针对 Google 的用户指南中所述的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);

您可以通过使用所有相同的CFLAGS编译foo.h来为这些创建模拟,这些CFLAGS将用于编译随附的生产foo.c

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

要在测试中使用它,请使用foo-mock.h声明的 API 设置期望值和返回foo-mock.h ,如上面的命令行调用所示。 包括 Google Test 的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++ 代码,而无需实际编写或维护其他模拟代码,您只需专注于测试。 从长远来看要容易一些。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM