2016-07-27 94 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(),你將不得不創建另一個庫和一個單獨的測試應用程序來鏈接它(或者在測試應用程序中動態加載和卸載庫)。

這聽起來相當費力,這就是爲什麼我拖延在我自己的應用程序中添加測試!

希望這會有所幫助!