在MSVC(Visual Studio)中模拟C函数

4
我正在阅读几篇关于模拟C函数的文章(如CMockCMocka),但我不确定在这个过程中实际的函数是如何被替换为模拟函数的。例如,CMocka依赖于使用GNU编译器的自动包装,支持参数如--wrap来在函数调用中添加__wrap前缀,或者使用弱符号允许您覆盖任何您喜欢的符号。
但是,对于几乎所有其他框架,在Visual Studio中如何做到这一点呢?
例如,CMock有一个类似于此的示例(这里大大简化):
// 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
}

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

这个问题询问关于配置链接器以包装函数的具体事情,但我认为在 MSVC 中不可能实现。此外,CMocka提到了这一点,但CMock根本没有提到这个要求。 - Lou
你在C++环境中必须模拟C风格的函数吗,还是只有C环境?如果可以使用C ++,那么你的问题有一个非常简单的解决方案,我可以提供一个例子。 - mrAtari
@mrAtari:这是Visual Studio,所以我可以混合使用C++和C,但是某些C代码可能与C++不兼容(例如,请查看此类似问题)。然而,如果大多数时间都能正常工作,请提供一个示例,说明C++如何解决这个问题 - 特别是如果它可以避免将任何测试相关的预处理器指令添加到生产代码中。 - Lou
2个回答

3

这里有一个使用Hippomocks的简单短小的解决方案:

我创建了一个空的Win32控制台应用程序,其中包括:

  • main.cpp
  • myfunc.c + myfunc.h
  • parsestuff.c, parsestuff.h

然后添加了您示例中的代码。

借助Hippomocks的帮助,您可以模拟每个C函数。以下是我的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

HippoMocks是一个免费、简单而强大的一头文件框架,可在GitHub上下载。

希望我赢得了悬赏 :)

更新,它的工作原理:

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

这是在我的机器上的样子:

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

如果你在内存窗口观察内存地址00D21C0D(可能会每次运行有所不同),你会发现,在ExpectCallFunc函数调用后,该地址被修改。


谢谢,我一会儿会检查一下,但你能解释一下这背后的机制吗?在链接阶段,myfunc.c中对ParseStuff的调用是如何被重定向的?好的,我也会看一下源代码,但我认为了解这些信息会很有价值。 :) - Lou
1
天哪,这是什么魔法?O_o - Lou
@Lousy:写这个的那个家伙真是个大巫师。:))) - mrAtari
好的,这听起来很不错。您有使用任何轻量级单元测试框架的经验吗?它应该能够与HippoMocks很好地配合使用。最好是支持JUnit XML的轻量级框架。 - Lou
1
很酷 :) 一个非常轻量级的测试运行器是YAFFUT - 只有一个头文件,使用起来比CppUnit简单得多。为了以正确的方式格式化输出,为什么不编写一个小脚本来处理格式化的事情呢? - mrAtari
是的,你说得对,我可能可以自己格式化它。到目前为止,我尝试了Google Test,因为它非常简单易用,支持自动测试注册,甚至还有Visual Studio的插件。不过,它也是C++,所以我需要在头文件包含周围使用extern "C"。好的,非常感谢你的回答,解决了我的大部分问题。我将保持悬赏开放几天,也许我可以得到一些额外的提示。 - Lou

0

我没有处理过C模拟库或Visual Studio,但我在自己的项目中考虑过这个问题。Feathers book建议使用预处理器接缝或链接接缝来处理此问题。你已经提到了预处理器接缝,所以我将重点放在链接接缝上。

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

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

听起来相当费力,这就是为什么我一直在拖延在自己的应用程序中添加测试!

希望这有所帮助!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接