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

在MSVC(Visual Studio)中模拟C函数,c,visual-studio,unit-testing,mocking,cmock,C,Visual Studio,Unit Testing,Mocking,Cmock,我正在阅读几篇关于模拟C函数(如、或)的文章,但我不确定在这个过程中如何用模拟函数替换实际函数。例如,CMocka依赖于使用GNU编译器的自动换行,GNU编译器支持像--wrap这样的参数将uuu wrap前缀附加到函数调用中,或者弱符号,允许您覆盖任何您喜欢的符号 但是在VisualStudio中,对于几乎所有其他框架,您是如何做到这一点的呢 例如,与此类似(此处简化了很多): 现在,在测试期间,Ruby脚本会创建模拟函数,如: // MockParseStuff.c (auto create

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

但是在VisualStudio中,对于几乎所有其他框架,您是如何做到这一点的呢

例如,与此类似(此处简化了很多):

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

// MockParseStuff.c (auto created by cmock)

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

  • 这是否意味着我不能在单元测试项目中包含
    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
    }
    
    //将ParseStuff替换为ParseStuff\u wrap
    #定义ParseStuff ParseStuff_包装
    //包含源而不是标头
    #包括
    #未定义的语法分析器
    int ParseStuff_wrap(char*cmd)
    {
    //这将从MyFunc调用,
    //它现在静态地包括在内
    }
    

    但是这看起来像是一个很大的管道,我甚至没有看到任何地方提到过它。

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

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

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

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


    希望这有帮助

    这里有一个简单而简短的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是一个免费、简单且功能强大的单头框架,可以在GitHub上下载

    希望我赢得了赏金:)

    更新,工作原理:

  • HippoMocks获取指向ParseStuff的func指针
  • HippoMocks构建一个替换func指针,指向具有相同签名和自己实现的模板函数
  • Hippomocks从内存中的函数调用序言中修补jmp操作码,以便它指向被替换的函数
  • 替换和内存补丁在调用后或在析构函数中释放
  • 下面是在我的机器上的外观:

    @ILT+3080(_ParseStuff):
    00D21C0D  jmp HippoMocks::mockFuncs<char,int>::static_expectation1<0,char *> (0D21DB1h)  
    
    @ILT+3080(\u):
    00D21C0D jmp HippoMocks::mockFuncs::静态预期1(0D21DB1h)
    

    如果您在内存窗口中查看内存地址00D21C0D(可能不同于不同的运行),您将看到,在调用ExpectCallFunc后,它会被修补。

    询问了有关配置链接器以包装函数的具体问题,但我相信在MSVC中是不可能的。CMocka也提到过这个问题,但是CMock根本没有提到这个要求。你是否需要在C++环境中模拟C风格的函数,或者你只有C环境吗?如果你可以使用C++,你的问题就有一个非常简单的解决方案,我可以提供一个例子。@ MrATARI:它是VisualStudio,所以我可以混合C++和C,但是可能存在某些C代码与C++不兼容的问题(例如,检查)。但是,如果它大部分时间都在工作,请提供一个例子,说明C++如何解决这个问题——特别是如果它避免将任何与测试相关的预处理器指令添加到生产代码中。谢谢,我马上检查一下,但是你能解释一下这个机制吗?在链接阶段,如何重定向
    myfunc.c
    中对
    ParseStuff
    的调用?好的,我也会看一下源代码,但我认为拥有这些信息是很有价值的O_o@Lousy:写这封信的那个人是个真正的巫师。:)好的,这看起来很酷。您是否有过任何轻量级单元测试框架的经验,这些框架与Hippomock配合得很好?一些轻量级的东西,首先是JUnit XML支持?它很酷:)一个非常轻量级的testrunner是YAFFUT,它只带有一个头,并且作为CPPFUnit使用更简单。为了以正确的方式格式化输出,为什么不编写一个小脚本,它负责格式化工作?
    #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
    }
    
    @ILT+3080(_ParseStuff):
    00D21C0D  jmp HippoMocks::mockFuncs<char,int>::static_expectation1<0,char *> (0D21DB1h)