【问题标题】:Mocking C functions in MSVC (Visual Studio)在 MSVC (Visual Studio) 中模拟 C 函数
【发布时间】:2016-08-02 21:16:19
【问题描述】:

我正在阅读几篇关于模拟 C 函数的文章(如 CMockCMocka),但我不确定在此过程中如何将实际函数替换为模拟函数。例如,CMocka 依赖于使用 GNU 编译器的自动包装,它支持像 --wrap 这样的参数,将 __wrap 前缀附加到函数调用,或者 weak symbols 允许您覆盖任何您喜欢的符号.

但是对于几乎所有其他框架,您如何在 Visual Studio 中执行此操作?

例如CMock has an example类似这样(这里简化了很多):

// myfunc.c
#include <parsestuff.h>

// this is the function we would like to test
int MyFunc(char* Command)
{
    // this is the call to the function we will mock
    return ParseStuff(Command);
}

还有实际的实现,它包含链接器在实际应用程序中应该找到的实际功能:

// parsestuff.c

int ParseStuff(char* cmd)
{
    // do some actual work
    return 42;
}

现在,在测试期间,Ruby 脚本会创建模拟函数,例如:

// MockParseStuff.c (auto created by cmock)

int ParseStuff(char* Cmd);
void ParseStuff_ExpectAndReturn(char* Cmd, int toReturn);
  1. 但是如果VS项目中已经包含parsestuff.c,那么来自myfunc.c的调用怎么可能以MockParseStuff.c结束呢?

  2. 这是否意味着我不能将parsestuff.c 包含在单元测试项目中?但是如果是这种情况,那么在任何测试中也不可能从myfunc.c 模拟MyFunc,因为我已经必须包含该文件才能对其进行测试?

(更新)我也知道我可以包含 .c 文件而不是 .h 文件,然后做一些预处理器来替换原始调用,例如:

// replace ParseStuff with ParseStuff_wrap
#define ParseStuff ParseStuff_wrap
// include the source instead of the header
#include <myfunc.c>
#undef ParseStuff

int ParseStuff_wrap(char* cmd) 
{
    // this will get called from MyFunc,
    // which is now statically included
}

但这似乎需要很多管道,我什至没有看到任何地方提到它。

【问题讨论】:

  • This question 询问了有关配置链接器以包装函数的具体问题,但我相信这在 MSVC 中是不可能的。另外,CMocka 提到了这一点,但 CMock 根本没有提到这个要求。
  • 你必须在 C++ 环境中模拟 C 风格的函数,还是只有 C 环境?如果你会使用 C++,有一个非常简单的解决方案可以解决你的问题,我可以提供一个示例。
  • @mrAtari:它是 Visual Studio,所以我可以混合使用 C++ 和 C,但是可能存在某些 C 代码与 C++ 不兼容的问题(例如,检查 this similar question)。但是,如果它在大多数情况下都有效,请务必提供 C++ 如何解决此问题的示例 - 特别是如果它可以避免将任何与测试相关的预处理器指令添加到生产代码中。

标签: c visual-studio unit-testing mocking cmock


【解决方案1】:

这是一个简单而简短的 hippomocks 解决方案:

我用

创建了一个空的 Win32 控制台应用程序
  • main.cpp
  • myfunc.c + myfunc.h
  • parsestuff.c、parsestuff.h

并添加了您示例中的代码。

在 hippomocks 的帮助下,您可以模拟每个 C-Function。这是我的 main.cpp 的样子:

#include "stdafx.h"
#include "myfunc.h"
#include "hippomocks.h"


extern "C" int ParseStuff(char* cmd);

int _tmain(int argc, _TCHAR* argv[])
{
    MockRepository mocks;

    mocks.ExpectCallFunc(ParseStuff).Return(4711);

    char buf[10] = "";

    int result = MyFunc(buf);

    return result; //assert result is 4711
}

HippoMocks 是一个免费、简单且非常强大的单头框架,可以在 GitHub 上下载。

希望我赢得了赏金 :)

更新,工作原理:

  1. HippoMocks 获取指向 ParseStuff 的 func 指针
  2. HippoMocks 构建了一个替换 func 指针,指向具有相同签名和自己实现的模板函数。
  3. Hippomocks 修补内存中函数调用序言中的 jmp 操作码,使其指向被替换的函数。
  4. 调用后或在析构函数中释放替换和内存补丁。

这是我机器上的样子:

@ILT+3080(_ParseStuff):
00D21C0D  jmp HippoMocks::mockFuncs<char,int>::static_expectation1<0,char *> (0D21DB1h)  

如果您在内存窗口中观察内存地址 00D21C0D(可能与运行不同),您会看到,它在调用 ExpectCallFunc 后得到了修补。

【讨论】:

  • 谢谢,我稍后会检查,但你能解释一下这背后的机制吗?在链接阶段如何重定向对ParseStuffin myfunc.c 的调用?好吧,我也会看一下源代码,但我认为拥有这些信息会很有价值。 :)
  • @Lousy:写它的人,是一个真正的巫师。 :)))
  • 好的,这看起来很酷。你有任何轻量级单元测试框架的经验,可以很好地与 HippoMocks 配合使用吗?轻量级的东西,最好是 JUnit XML 支持?
  • 很酷 :) YAFFUT 是一个非常轻量级的测试运行程序 - 仅带有一个标头,并且作为 CppUnit 使用要简单得多。为了以正确的方式格式化输出,你为什么不写一个小脚本来做格式化的东西?
【解决方案2】:

我没有处理过 C 模拟库或 Visual Studio,但我在自己的项目中考虑过这一点。 Feathers book 建议使用预处理器接缝或链接接缝作为处理此问题的工具。您已经提到了预处理器接缝,所以我将重点介绍链接接缝。

链接缝要求模拟函数在库中,模拟函数在库中。测试可以链接到模拟函数库,而目标应用程序可以链接到原始库。

当然,正如您所提到的,要模拟 MyFunc(),您必须创建另一个库和一个单独的测试应用程序来链接它(或在测试应用程序中动态加载和卸载库)。

这听起来很费力,这就是为什么我要拖延在自己的应用程序中添加测试的原因!

希望这会有所帮助!

【讨论】:

    猜你喜欢
    • 2016-02-08
    • 1970-01-01
    • 2018-10-07
    • 2014-01-19
    • 2020-07-03
    • 2021-02-14
    • 2015-07-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多