2012-01-22 12 views
11

Chciałbym napisać testy dla biblioteki C, w C. Chciałbym wyśmiewać niektóre funkcje do testu.Funkcja szyderstwa (do testowania) w C?

że moja biblioteka jest skompilowany z następującego źródła:

/* 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); 
} 

Chcę napisać test takiego:

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

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

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

Czy jest jakiś sposób mogę skompilować tak że myfunction użyje definicja square w foo_test.c, zamiast jednej w foo.c, tylko podczas łączenia pliku wykonywalnego foo_test? To znaczy, chcę skompilować foo.c do biblioteki (nazwijmy to libfoo.so), a następnie skompilować foo_test.c z libfoo.so i trochę magii, aby uzyskać plik wykonywalny foo_test, który wykorzystuje inną implementację square.

Byłoby pomocne usłyszeć rozwiązania, gdy square nie jest zadeklarowany static, ale rozwiązanie powyższego przypadku byłoby jeszcze lepsze.

EDYCJA: Wydaje się beznadziejne, ale oto pomysł: Załóżmy, że kompiluję się z -O0 -g, więc jest mało prawdopodobne, że square zostanie wstawiony i powinienem mieć symbole pokazujące, gdzie połączenie zostało rozwiązane. Czy istnieje sposób, aby wkraść się do pliku obiektu i zamienić rozstrzygnięte odniesienie?

+0

'LD_PRELOAD' może ci to dać, ale mam nadzieję, że ktoś inny ma lepszą odpowiedź. – sarnold

+3

Nie dla funkcji statycznych – bdonlan

+1

Nie sądzę, że 'LD_PRELOAD' zamierza to zrobić. Chcę móc zmienić funkcję (która może być statyczna) wewnątrz biblioteki, która jest już skompilowana. Jak rozumiem (a może to nieprawda), symbol 'square' wewnątrz' myfunction' zostanie rozwiązany, zanim spróbuję połączyć 'foo_test'. – leif

Odpowiedz

4

Wygląda używasz GCC, więc można użyć słabego atrybut:

Słabi atrybut powoduje deklarację być emitowane jako słaby symbolem, a nie globalny. Jest to przydatne przede wszystkim przy definiowaniu funkcji bibliotecznych, które można zastąpić w kodzie użytkownika,, ale można również używać z deklaracjami niefunkcjonalnymi. Słabe symbole są obsługiwane dla celów ELF, a także dla celów a.out, gdy używa się asemblera GNU i łącznika GNU .

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

+0

Dzięki. Mogę prawdopodobnie wziąć to stąd, ale czy możesz dać mi jakieś wskazówki dotyczące modyfikowania słabego odniesienia? – leif

+1

Po prostu zdefiniuj funkcję o tej samej nazwie, która NIE jest słaba, wtedy powinna zastąpić słabą funkcję w bibliotece. –

4

Nie, nie ma na to rozwiązania. Jeśli w zasięgu znajduje się funkcja o nazwie pasującej do wywołania funkcji w pliku źródłowym, funkcja ta będzie używana. Żadne oszustwo z deklaracji nie będzie rozmawiać z kompilatorem. Do czasu aktywacji linkera odniesienie do nazwy zostanie już rozwiązane.

0

W przypadkach takich jak Twoja używam Typemock Isolator++ API.

Umożliwia zastąpienie oryginalnego zachowania metody własną implementacją. Jednak ze względu na specyfikę języka należy unikać takich samych nazw dla funkcji pod i do testów.

#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); 
    } 
}; 

Mam nadzieję, że przyda Ci się, powodzenia!

4

Napisałem Mimick, bibliotekę szyderczą/skrótową dla funkcji języka C, które rozwiązują ten problem.

Przyjmując, że kwadrat nie jest statyczny ani wbudowany (ponieważ w przeciwnym razie zostanie powiązany z jednostką kompilacji i funkcjami, które jej używa) i że twoje funkcje są kompilowane we wspólnej bibliotece o nazwie "libfoo.so" (lub czymkolwiek innym konwencja nazewnictwa platformy jest), to co byś zrobił:

#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); 
} 

jest to pełnowartościowy rozwiązanie szyderczy choć i jeśli chcesz tylko skrótową square (i nie dbają o interakcjach testowych), można zrobić coś podobnego:

#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 działa za pomocą introspekcji na uruchomionym pliku wykonywalnym i zatruwaniu w czasie wykonywania globalnej tabeli przesunięć, aby przekierować funkcje do wybranego przez nas kodu pośredniczącego.