Функции 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);
  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
}

но это похоже на много слесарного дела, и я даже не вижу нигде упомянутого.

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.

Надеюсь, я получил награду:)

ОБНОВЛЕНИЕ, Как это работает:

  1. HippoMocks получает функциональный указатель на ParseStuff
  2. HippoMocks создает замещающий указатель func на функцию шаблона с такой же сигнатурой и собственной реализацией.
  3. Hippomocks исправляет код операции jmp из пролога вызова функции в памяти, чтобы он указывал на замененную функцию.
  4. Замена и патч памяти выпускаются после вызова или в деструкторе.

Вот как это выглядит на моей машине:

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

Если вы посмотрите адрес памяти 00D21C0D (может отличаться от запуска к запуску) в окне памяти, вы увидите, что он исправляется после вызова ExpectCallFunc.

Я не имел дело с библиотеками-макетами C или Visual Studio, но я думал об этом в своем собственном проекте. Книга " Перья" предлагает шов препроцессора или шов ссылки в качестве инструмента для решения этой проблемы. Вы уже упомянули шов препроцессора, поэтому я сосредоточусь на шве ссылки.

Шов ссылки требует, чтобы макетная функция находилась в библиотеке, а макетная функция была в библиотеке. Тест может связываться с библиотекой фиктивных функций, в то время как целевое приложение может связываться с исходной библиотекой.

Конечно, как вы упоминаете, чтобы смоделировать MyFunc(), вам нужно будет создать другую библиотеку и отдельное тестовое приложение для связи с ним (или динамически загружать и выгружать библиотеки в тестовом приложении).

Это звучит довольно трудоемко, поэтому я откладываю добавление тестов в собственное приложение!

Надеюсь это поможет!

Другие вопросы по тегам