Функции Mocking C в MSVC (Visual Studio)
Я читаю несколько статей о поддельных функциях C (таких как CMock или CMocka), но я не уверен, как в этом процессе фактические функции заменяются на поддельные функции. Например, 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);
Но если проект VS уже включает
parsestuff.c
как это возможно, что звонок отmyfunc.c
заканчивается вMockParseStuff.c
?Значит ли это, что я не могу иметь
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
}
но это похоже на много слесарного дела, и я даже не вижу нигде упомянутого.
2 ответа
Вот простое и короткое решение с гиппопотамами:
Я создал пустое консольное приложение Win32 с
- main.cpp
- myfunc.c + myfunc.h
- parsestuff.c, parsestuff.h
и добавил код из вашего примера.
С помощью гиппопотамов вы можете издеваться над каждой С-функцией. Вот как выглядит мой 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
- HippoMocks создает замещающий указатель func на функцию шаблона с такой же сигнатурой и собственной реализацией.
- Hippomocks исправляет код операции jmp из пролога вызова функции в памяти, чтобы он указывал на замененную функцию.
- Замена и патч памяти выпускаются после вызова или в деструкторе.
Вот как это выглядит на моей машине:
@ILT+3080(_ParseStuff):
00D21C0D jmp HippoMocks::mockFuncs<char,int>::static_expectation1<0,char *> (0D21DB1h)
Если вы посмотрите адрес памяти 00D21C0D (может отличаться от запуска к запуску) в окне памяти, вы увидите, что он исправляется после вызова ExpectCallFunc.
Я не имел дело с библиотеками-макетами C или Visual Studio, но я думал об этом в своем собственном проекте. Книга " Перья" предлагает шов препроцессора или шов ссылки в качестве инструмента для решения этой проблемы. Вы уже упомянули шов препроцессора, поэтому я сосредоточусь на шве ссылки.
Шов ссылки требует, чтобы макетная функция находилась в библиотеке, а макетная функция была в библиотеке. Тест может связываться с библиотекой фиктивных функций, в то время как целевое приложение может связываться с исходной библиотекой.
Конечно, как вы упоминаете, чтобы смоделировать MyFunc(), вам нужно будет создать другую библиотеку и отдельное тестовое приложение для связи с ним (или динамически загружать и выгружать библиотеки в тестовом приложении).
Это звучит довольно трудоемко, поэтому я откладываю добавление тестов в собственное приложение!
Надеюсь это поможет!