Функция 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 работает, используя некоторую интроспекцию исполняемого исполняемого файла и отравляя во время выполнения глобальную таблицу смещения для перенаправления функций на заглушку по нашему выбору.
Ответ 4
То, что вы ищете, описано в этой статье: Модульное тестирование с макетными объектами в C
Ответ 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);
}
};
Надеюсь, это будет полезно для вас, удачи!