2016-07-27 80 views
2

我正在阅读几篇关于嘲讽C函数的文章(如CMockCMocka),但我不确定在此过程中实际函数如何替换为模拟函数。例如,CMocka依靠使用GNU编译器的自动换行功能,该编译器支持--wrap等参数以将__wrap前缀附加到函数调用,或弱符号,它们允许您覆盖任何您喜欢的符号。在MSVC(Visual Studio)中嘲笑C函数

但是,你如何在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,从myfunc.c,因为我已经不得不包含它来测试它的文件了吗?

(更新)我也知道,我可以包括.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 
} 

但是这看起来像很多管道,我甚至都没有在任何地方看到它。

+0

[这个问题](http://stackoverflow.com/q/33790425/1488067)问有关配置链接器来包装函数的具体事情,但我相信它不可能在MSVC中。此外,CMocka提到了这一点,但CMock根本没有提到这个要求。 – Lou

+0

您是否需要在C++环境中模拟C-Style函数,还是只有C环境?如果您可以使用C++,那么您的问题就有一个非常简单的解决方案,我可以提供一个示例。 – mrAtari

+0

@mrAtari:它是Visual Studio,所以我可以混合使用C++和C,但是某些C代码可能与C++不兼容(检查[类似的问题])(http://softwarerecs.stackexchange.com/q/ 34878/22983))。但是,如果大多数情况下工作正常,那么请提供一个例子说明C++如何解决这个问题 - 特别是如果它可以避免在生产代码中添加任何与测试相关的预处理器指令。 – Lou

回答

1

下面是与hippomocks简单和短期的解决方案:

我创建了

  • 的main.cpp
  • myfunc.c + myfunc.h
  • parsestuff.c一个空的Win32控制台应用程序,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是一个免费的,简单的和非常强大的一个头框架,可以在GitHub上下载。

希望我已经赢得了奖金:)

UPDATE,它是如何工作:

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

这是它看起来像我的机器上:

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

如果你看的内存地址00D21C0D(可以运行不同的运行),在内存中的窗口,你会看到,它获取后修补ExpectCallFunc的调用。

+0

谢谢,我会在一会儿检查一下,但你能解释一下这个机制吗?在链接阶段,对'myfunc.c'中的'ParseStuff'的调用如何重定向?那么,我也会看看源代码,但我认为获得这些信息会很有价值。 :) – Lou

+0

[神的母亲,这是什么巫师?](https://github.com/dascandy/hippomocks/blob/master/HippoMocks/hippomocks.h)O_o – Lou

+0

@Lousy:写这个的人是一个真正的巫师。 :))) – mrAtari

0

我还没有处理C嘲讽库或Visual Studio,但我已经在我自己的项目中想到了这一点。 Feathers book建议预处理器接缝或链接接缝作为处理这种情况的工具。你已经提到了预处理器接缝,所以我会专注于链接接缝。

链接缝要求模拟函数在库中,模拟函数在库中。测试可以链接到模拟函数库,而目标应用程序可以链接到原始库。当然,正如你所提到的,为了模拟MyFunc(),你将不得不创建另一个库和一个单独的测试应用程序来链接它(或者在测试应用程序中动态加载和卸载库)。

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

希望这会有所帮助!