Функция mocking (для тестирования) в C?

Я хотел бы написать тесты для библиотеки C, в C. Я хотел бы издеваться над некоторыми функциями для теста.

Предположим, что моя библиотека скомпилирована из следующего источника:

/* foo.h */
int myfunction(int x, int y);

/* foo.c */
#include "foo.h"

static int square(int x) { return x * x; }

int myfunction(int x, int y) {
    return square(x) + square(y);
}

Я хочу написать тест следующим образом:

/* foo_test.c */
#include "foo.h"

static int square(int x) { return x + 1; }

int main(void) {
    assert(myfunction(0, 0) == 2);
    return 0;
}

Можно ли каким-либо образом скомпилировать, чтобы myfunction использовал определение square в foo_test.c вместо имени в foo.c только при связывании исполняемого файла foo_test? То есть, я хочу скомпилировать foo.c в библиотеку (пусть ее называют libfoo.so), а затем скомпилировать foo_test.c с помощью libfoo.so и некоторой магией, чтобы получить исполняемый файл foo_test, который использует разные реализация square.

Было бы полезно услышать решения, если square не объявлен static, но решение вышеприведенного случая было бы еще лучше.

EDIT: кажется безнадежным, но вот идея: предположим, что я скомпилирую с -O0 -g, поэтому маловероятно, что square получит inline, и у меня должны быть символы, показывающие, где был разрешен вызов. Есть ли способ проникнуть в объектный файл и заменить найденную ссылку?

Ответ 1

Похоже, вы используете GCC, поэтому можете использовать слабый атрибут:

Слабый атрибут заставляет объявление выдаваться как слабый а не глобальным. Это в первую очередь полезно при определении библиотечные функции, которые могут быть переопределены в коде пользователя,, хотя он может также могут использоваться с не-функциональными объявлениями. Слабые символы поддерживается для целей ELF, а также для целей a.out при использовании GNU-сборщик и компоновщик.

http://gcc.gnu.org/onlinedocs/gcc/Function-Attributes.html

Ответ 2

Нет, для этого нет решения. Если в области есть функция с именем, соответствующим вызову функции в исходном файле, эта функция будет использоваться. Никакой трюк объявления не собирается говорить компилятору из него. К тому времени, когда компоновщик активен, ссылка на имя будет уже решена.

Ответ 3

Я написал Mimick, издевательскую /stubbing библиотеку для функций C, которые обращаются к этому.

Предполагая, что квадрат не является статичным и нестрочным (поскольку в противном случае он привязывается к модулю компиляции и к функциям, которые его используют) и что ваши функции скомпилированы в общей библиотеке с именем "libfoo.so" (или независимо от вашей платформы соглашение об именах), это то, что вы бы сделали:

#include <stdlib.h>
#include <assert.h>
#include <mimick.h>

/* Define the blueprint of a mock identified by `square_mock`
   that returns an `int` and takes a `int` parameter. */
mmk_mock_define (square_mock, int, int);

static int add_one(int x) { return x + 1; }

int main(void) {
    /* Mock the square function in the foo library using 
       the `square_mock` blueprint. */
    mmk_mock("[email protected]:foo", square_mock);

    /* Tell the mock to return x + 1 whatever the given parameter is. */
    mmk_when(square(mmk_any(int)), .then_call = (mmk_fn) add_one);

    /* Alternatively, tell the mock to return 1 if called with 0. */
    mmk_when(square(0), .then_return = &(int) { 1 });

    assert(myfunction(0, 0) == 2);

    mmk_reset(square);
}

Это полномасштабное насмешливое решение, и если вы хотите только заглушить square (и не заботитесь о тестировании взаимодействий), вы можете сделать что-то подобное:

#include <stdlib.h>
#include <assert.h>
#include <mimick.h>

static int my_square(int x) { return x + 1; }

int main(void) {
    mmk_stub("[email protected]:foo", my_square);

    assert(myfunction(0, 0) == 2);

    mmk_reset(square);
}

Mimick работает, используя некоторую интроспекцию исполняемого исполняемого файла и отравляя во время выполнения глобальную таблицу смещения для перенаправления функций на заглушку по нашему выбору.

Ответ 5

В подобных случаях я использую Typemock Isolator ++ API.

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

#include "foo.h"

static int square_test(int x) { return x + 1; }

TEST_CLASS(ArgumentTests)
{
public:
    TEST_METHOD_CLEANUP(TearDown)
    {
        ISOLATOR_CLEANUP();
    }

    TEST_METHOD(TestStaticReplacedStatic)
    {
        PRIVATE_WHEN_CALLED(NULL, square).DoStaticOrGlobalInstead(square_test, NULL);
        Assert::IsTrue(myfunction(0, 0) == 2);
    }
};

Надеюсь, это будет полезно для вас, удачи!