Функция mocking (для тестирования) в C? - PullRequest
11 голосов
/ 22 января 2012

Я хотел бы написать тесты для библиотеки 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, но решение вышеуказанного случая было бы еще лучше.

РЕДАКТИРОВАТЬ: Этокажется безнадежным, но вот идея: предположим, что я компилирую с -O0 -g, поэтому маловероятно, что square будет встроенным, и у меня должны быть символы, показывающие, где был разрешен вызов.Есть ли способ проникнуть в объектный файл и обменяться разрешенной ссылкой?

Ответы [ 5 ]

7 голосов
/ 10 апреля 2016

Я написал Mimick , библиотеку насмешек / заглушек для функций 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("square@lib: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("square@lib:foo", my_square);

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

    mmk_reset(square);
}

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

5 голосов
/ 23 января 2012

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

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

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

5 голосов
/ 22 января 2012

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

2 голосов
/ 02 августа 2013

То, что вы ищете, описано в этой статье: Модульное тестирование с фиктивными объектами в C

0 голосов
/ 03 марта 2016

В таких случаях, как ваш, я использую 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);
    }
};

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...