2012-01-22 7 views
11

Me gustaría escribir pruebas para una biblioteca C, en C. Me gustaría burlar algunas funciones para la prueba.Función burlarse (para probar) en C?

Supongamos que mi biblioteca se compila a partir de la siguiente fuente:

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

Quiero escribir una prueba como esta:

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

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

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

¿Hay alguna manera de que pueda compilar de manera que myfunction utilizará la definición de square en foo_test.c, en lugar del uno en foo.c, solo cuando se vincula el ejecutable foo_test? Es decir, quiero compilar foo.c en una biblioteca (llamémoslo libfoo.so), y luego compilar foo_test.c con libfoo.so y algo de magia para que obtenga un ejecutable foo_test que usa la implementación diferente de square.

Sería útil escuchar soluciones para cuando square no se declare static, pero resolver el caso anterior sería aún mejor.

EDITAR: Parece imposible, pero aquí hay una idea: Supongamos que compilo con -O0 -g, por lo que es poco probable que square se inserte y debería tener símbolos que muestren dónde se resolvió la llamada. ¿Hay alguna manera de colarse en el archivo objeto y cambiar la referencia resuelta?

+0

'LD_PRELOAD' puede darte esto, pero espero que alguien más tenga una mejor respuesta. – sarnold

+3

No para funciones estáticas – bdonlan

+1

No creo que 'LD_PRELOAD' vaya a hacerlo. Quiero poder cambiar una función (que puede ser estática) dentro de una biblioteca que ya está compilada. Según tengo entendido (y quizás esto sea falso), el símbolo 'square' dentro de' myfunction' ya se habrá resuelto antes de intentar vincular 'foo_test'. – leif

Respuesta

4

Parece que está utilizando GCC, por lo que puede utilizar el atributo débil:

El atributo débil hace que la declaración que será emitida como un símbolo débil en lugar de un mundial. Esto es principalmente útil en la definición de las funciones de la biblioteca que pueden anularse en el código de usuario, aunque puede también usarse con declaraciones de no función. Los símbolos débiles son compatibles con objetivos ELF, y también para objetivos a.out cuando se utiliza el ensamblador y enlazador GNU .

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

+0

Gracias. Probablemente pueda tomarlo desde aquí, pero ¿pueden darme algún consejo sobre cómo modificar una referencia débil? – leif

+1

Simplemente defina una función con el mismo nombre que NO sea débil, entonces debe anular la función débil en la biblioteca. –

4

No, no hay solución para esto. Si hay una función en el alcance con un nombre que coincida con una llamada de función dentro de un archivo fuente, se usará esa función. Ningún engaño de declaración va a sacar al compilador de eso. Para cuando el enlazador esté activo, la referencia del nombre ya habrá sido resuelta.

0

En casos como el suyo utilizo Typemock Isolator++ API.

Le permite reemplazar el comportamiento original del método por su propia implementación. Pero, debido a especificaciones específicas del lenguaje, debe evitar los mismos nombres para las funciones bajo y para las pruebas.

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

Espero que sea útil para usted, ¡buena suerte!

4

Escribí Mimick, una biblioteca de burla/stubbing para funciones C que solucionan esto.

Suponiendo que la plaza no es estática ni en línea (porque de lo contrario se llega a unir a la unidad de compilación y las funciones que lo utiliza) y que sus funciones son compilados dentro de una biblioteca compartida llamada "libfoo.so" (o lo que su convención de nombres de plataforma es), esto es lo que haría:

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

esta es una solución burlarse completo soplado embargo, y si sólo desea código auxiliar square (y no se preocupan por las interacciones de prueba), se puede hacer algo similar:

#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 funciona mediante el uso de alguna introspección en el ejecutable en ejecución y el envenenamiento en el tiempo de ejecución de la tabla de compensación global para redirigir las funciones al stub de nuestra elección.

Cuestiones relacionadas