2008-10-31 13 views
6

Utilizamos un modelo de objeto simple para nuestro código de red de bajo nivel en el trabajo donde los punteros struct se pasan a las funciones que se hacen pasar por métodos. Heredé la mayor parte de este código, que fue escrito por consultores con una experiencia C/C++ pasable en el mejor de los casos y he pasado muchas noches tratando de refactorizar el código en algo que se asemejaría a una estructura razonable.¿Cómo me burlo de objetos sin herencia (en C)?

Ahora me gustaría llevar el código bajo prueba unitaria, pero teniendo en cuenta el modelo de objeto que hemos elegido, no tengo idea de cómo simular objetos. Véase el siguiente ejemplo:

cabecera de la muestra (foo.h):

#ifndef FOO_H_ 
#define FOO_H_ 

typedef struct Foo_s* Foo; 
Foo foo_create(TcpSocket tcp_socket); 
void foo_destroy(Foo foo); 
int foo_transmit_command(Foo foo, enum Command command); 

#endif /* FOO_H_ */ 

fuente de la muestra (foo.c):

struct Foo_s { 
    TcpSocket tcp_socket; 
}; 

Foo foo_create(TcpSocket tcp_socket) 
{ 
    Foo foo = NULL; 

    assert(tcp_socket != NULL); 

    foo = malloc(sizeof(struct Foo_s)); 
    if (foo == NULL) { 
     goto fail; 
    } 
    memset(foo, 0UL, sizeof(struct Foo_s)); 

    foo->tcp_socket = tcp_socket; 

    return foo; 

fail: 
    foo_destroy(foo); 
    return NULL; 
} 

void foo_destroy(Foo foo) 
{ 
    if (foo != NULL) { 
      tcp_socket_destroy(foo->tcp_socket); 
      memset(foo, 0UL, sizeof(struct Foo_s)); 
      free(foo); 
    } 
} 

int foo_transmit_command(Foo foo, enum Command command) 
{ 
    size_t len = 0; 
    struct FooCommandPacket foo_command_packet = {0}; 

    assert(foo != NULL); 
    assert((Command_MIN <= command) && (command <= Command_MAX)); 

    /* Serialize command into foo_command_packet struct */ 
    ... 

    len = tcp_socket_send(foo->tcp_socket, &foo_command_packet, sizeof(foo_command_packet)); 
    if (len < sizeof(foo_command_packet)) { 
      return -1; 
    } 
    return 0; 
} 

En el ejemplo anterior me gustaría burlarse de la TcpSocket objeto para que pueda traer "foo_transmit_command" bajo prueba unitaria, pero no estoy seguro de cómo hacerlo sin herencia. Realmente no quiero rediseñar el código para usar vtables a menos que realmente tenga que hacerlo. Tal vez hay un mejor enfoque para esto que burlarse?

Mi experiencia en pruebas proviene principalmente de C++ y tengo un poco de miedo de haberme arrinconado aquí. Agradecería cualquier recomendación de probadores más experimentados.

Editar:
Como Richard Quirk señaló que es realmente la llamada a "tcp_socket_send" que quiero anular y yo preferiría hacerlo sin quitar el símbolo tcp_socket_send real desde la biblioteca al vincular la prueba ya es llamado por otras pruebas en el mismo binario.

estoy empezando a pensar que no hay una solución obvia a este problema ..

Respuesta

3

Puede utilizar macro para redefinir tcp_socket_send a tcp_socket_send_moc y el enlace con la implementación real y ficticia para tcp_socket_sendtcp_socket_send_moc.
tendrá que seleccionar cuidadosamente el lugar adecuado para:

#define tcp_socket_send tcp_socket_send_moc 
+0

Probablemente podría ponerlo en el encabezado TcpSocket y encerrarlo en #ifdef TESTING .. #endif. Buena sugerencia. –

+0

Conoces mejor tu código, solo quería decir que la macro es una cosa total y que la macro colocada en el lugar incorrecto puede llevar al problema que realmente es difícil de depurar – Ilya

0

No está seguro de lo que quiere lograr.

puede agregar todas loquesea _ * funciones como miembros del puntero de función a struct Foo_s pero todavía se necesita para pasar de forma explícita puntero a su objeto ya que no hay implícita this en C. Sin embargo, se le dará la encapsulación y polimorfismo.

+0

Eso requeriría que reescriba una gran parte de la base de código "solo" para obtener el código en pruebas unitarias, lo que preferiría evitar. –

0

¿Qué sistema operativo está utilizando? Creo que podría hacer una anulación con LD_PRELOAD en GNU/Linux: This slide looks useful.

+0

Linux, pero en algunos casos todas las dependencias viven en la misma biblioteca, por lo que si precompuso otra biblioteca también anularía el objeto que estoy probando. –

+0

No, el vinculador solo encontraría el símbolo único - tcp_socket_send - en LD_PRELOAD, otros estarían vinculados como de costumbre. –

2

Tenga una mirada en TestDept: http://code.google.com/p/test-dept/

Es un proyecto de código abierto que tiene como objetivo proporcionar posibilidad de tener implementaciones alternativas, por ejemplo, stubs, de funciones y poder cambiar en tiempo de ejecución qué implementación de dicha función usar.

Todo se logra manipulando archivos de objeto, que se describe muy bien en la página de inicio del proyecto.

+0

+1 - Esto parece una gran opción para probar código incrustado de alto nivel, especialmente aquellos en el espacio de la aplicación o espacio de infraestructura. ¡Gracias! – tehnyit

0

Usar la macro para refinar tcp_socket_send es bueno. Pero el simulacro solo devuelve un comportamiento. O necesita implementar alguna variable en la función simulada y configurarla de manera diferente antes de cada caso de prueba. Otra forma es cambiar tcp_socket_send al punto de función. Y lo señala a una función simulada diferente para diferentes casos de prueba.

1

Alternativamente, puede usar TestApe TestApe Unit testing for embedded software - Puede hacerlo, pero tenga en cuenta que es C solamente. Se iría así ->

int mock_foo_transmit_command(Foo foo, enum Command command) { 
    VALIDATE(foo, a); 
    VALIDATE(command, b); 
} 

void test(void) { 
    EXPECT_VALIDATE(foo_transmit_command, mock_foo_transmit_command); 
    foo_transmit_command(a, b); 
} 
0

Para añadir a la respuesta de Ilya. Puedes hacerlo.

#define tcp_socket_send tcp_socket_send_moc 
#include "your_source_code.c" 

int tcp_socket_send_moc(...) 
{ ... } 

I tienen la técnica de incluir el archivo de origen en el módulo de prueba de la unidad para reducir al mínimo las modificaciones en el archivo de fuente cuando se crea pruebas de unidad.

Cuestiones relacionadas