una tipos solicita sistema reglas operativo modos miscelanea manejo llamadas llamada ejemplos ejecución como c++ unit-testing mocking

c++ - tipos - reglas de llamadas al sistema



Asesoramiento sobre llamadas al sistema burlón (6)

3 opciones

1 . Usa las habilidades de burla del enlazador gnu, la opción --wrap . Nunca he usado esto para probar el código de producción, ya que no me enteré hasta que nuestro equipo de desarrollo se había comprometido con el método 3. Desearía haberlo encontrado antes.

ld --wrap=getaddrinfo /*the rest of the link line*/ or g++ -Wl,--wrap=getaddrinfo /* the rest of the build line*/ // this in the unit tests. bool g_getaddrinfo_use_real = true; int g_getaddrinfo_ret = -1; int g_getaddrinfo_errno = something; int __wrap_getaddrinfo( const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res ) { if( g_getaddrinfo_use_real ) return __real_getaddrinfo(node,service,hints,res); errno = g_getaddrinfo_errno; return g_getaddrinfo_ret; }

2 . Defina su propio getaddrinfo y vincúlelo estáticamente a su aplicación de prueba. Esto solo funcionará si libc está vinculado dinámicamente, lo que es cierto el 99% del tiempo. Este método también tiene la desventaja de desactivar permanentemente el getaddrinfo real en su aplicación de prueba de unidad, pero es increíblemente fácil de implementar.

int g_getadderinfo_ret = -1; int g_getaddrinfo_errno = something; int getaddrinfo( const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res ) { errno = g_getaddrinfo_errno return g_getaddrinfo_ret; }

3 . Define tu propia función intermediaria con el mismo nombre. Entonces aún puedes llamar al original si quieres. Esto es mucho más fácil con algunas macros para ayudar con la repetición. También tendrá que usar extensiones gnu si quiere simular funciones variadas ( printf , open , etc.).

typedef (*getaddrinfo_func_type)( const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res ); getaddrinfo_func_type g_getaddrinfo_func; int getaddrinfo( const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res ) { return g_getaddrinfo_func( node, service, hints, res ) } int g_mock_getadderinfo_ret = -1; int g_mock_getaddrinfo_errno = something; int mock_getaddrinfo( const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res ) { errno = g_mock_getaddrinfo_errno; return g_mock_getaddrinfo_ret; } // use the original g_getaddrinfo_func = dlsym(RTDL_NEXT, "getaddrinfo"); // use the mock version g_getaddrinfo_func = &mock_getaddrinfo;

Tengo una clase que llama a getaddrinfo para buscar DNS. Durante la prueba quiero simular varias condiciones de error que involucran esta llamada al sistema. ¿Cuál es el método recomendado para burlarse de llamadas al sistema como esta? Estoy usando Boost.Test para las pruebas de mi unidad.


Aunque técnicamente es posible, no creo que sea factible. Tendría que poder reemplazar la implementación de esa función y probablemente no pueda y aún pueda enlazar a la biblioteca estándar en su sistema.

Lo que debes hacer es llamar a un intermediario. Entonces puede burlarse del intermediario durante la prueba y simplemente reenviar a la función real en producción. Incluso podría considerar la creación de una clase que interactúe con esta función y otras similares, y brinde una interfaz más genérica para su programa. Esta clase en realidad no haría más que reenviar llamadas la mayor parte del tiempo, pero durante la prueba se podría burlar de manera efectiva y se puede probar lo que sea que se use.

La cuestión es mantener cosas como esta, cosas que no se pueden probar, envueltas en algo tan trivial que realmente no necesita pruebas y luego burlarse de esa envoltura para probar las interacciones más complejas. KISS es especialmente importante aquí.


Buscar patrones para "Inyección de dependencia".

La Inyección de Dependencia funciona de la siguiente manera: en lugar de llamar a getaddrinfo directamente en su código, el código usa una interfaz que tiene un método virtual "getaddrinfo".

En el código de la vida real, el que llama pasa una implementación de la interfaz que mapea el método virtual "getaddrinfo" de la interfaz a la función real :: getaddrinfo.

En las pruebas unitarias, el que llama pasa una implementación que puede simular fallas, probar condiciones de error, ... ser breve: simular cualquier cosa que quiera simular.

EDITAR: Lee "Trabajar eficazmente con el código heredado" de Michael Feathers para obtener más consejos.


Descargo de responsabilidad: escribí ELFspy.

Puede usar ELFspy en una prueba unitaria para redirigir todas las llamadas a getaddrinfo en sus bibliotecas a una implementación propia proporcionándole una función con la misma firma.

Ejemplo:

int myaddrinfo(const char* node, const char* service, const struct addrinfo* hints, struct addrinfo** res) { return EAI_NODATA; } int main(int argv, char** argc) { spy::initialise(argc, argv); auto gai_spy = SPY(&getaddrinfo); auto gai_fake = spy::fake(gai_spy, &myaddrinfo); ... }

Puede hacer llamadas al getaddrinfo original si lo necesita de la siguiente manera:

gai_spy.invoke_real(node, service, hints, res);

Su código debe compilarse con -fPIC como código de posición independiente para que esto funcione.

Más detalles en el ejemplo sobre el tiempo de imitación (time_t *) se pueden encontrar aquí: https://github.com/mollismerx/elfspy/wiki/Example-03:-Faking-time


En los sistemas ELF, puede usar elf_hook para reemplazar temporalmente los símbolos dinámicamente vinculados.

Le permite crear una función arbitraria y reemplazar una función vinculada dinámicamente con ella.

  • Crear una biblioteca compartida que contenga el código bajo prueba
  • Cree una prueba que cargue la biblioteca compartida dinámicamente ( dlopen )
  • Redirige los símbolos que quieres simular a tus funciones de prueba

elf_hook tiene la siguiente firma:

void* elf_hook(char const* library_filename, void const* library_address, char const* function_name, void const* substitution_address);

lo que usarías es así:

int hooked_getaddrinfo(const char* node, const char* service, const struct addrinfo* hints, struct addrinfo** res) { return 42; } const char* lib_path = "path/to/library/under/test.so"; void* lib_handle = dlopen(lib_path, RTLD_LAZY); elf_hook(lib_path, LIBRARY_ADDRESS_BY_HANDLE(lib_handle), "getaddrinfo", hooked_getaddrinfo);

Cualquier llamada a getaddrinfo desde la biblioteca bajo prueba ahora llamará a hooked_getaddrinfo .

Un artículo completo del autor de elf_hook, Anthony Shoumikhin, está here .


En este caso, no es necesario que se burle de getaddrinfo , sino que debe probarlo sin depender de su funcionalidad. Tanto Patrick como Noah tienen buenos puntos, pero tienes al menos otras dos opciones:

Opción 1: Subclase para probar

Como ya tiene su objeto en una clase, puede hacer una subclase para probar. Por ejemplo, suponga que la siguiente es su clase real:

class DnsClass { int lookup(...); }; int DnsClass::lookup(...) { return getaddrinfo(...); }

Entonces, para probar, harías una subclase como esta:

class FailingDnsClass { int lookup(...) { return 42; } };

Ahora puede usar la subclase FailingDnsClass para generar errores, pero aún así verificar que todo se comporta correctamente cuando se produce una condición de error. Inyección de dependencia es a menudo su amigo en este caso.

NOTA: Esto es bastante similar a la respuesta de Patrick, pero (afortunadamente) no implica cambiar el código de producción si todavía no está configurado para la inyección de la dependencia.

Opción 2: utilizar una costura de enlace

En C ++, también tiene vetas de tiempo de enlace que Michael Feathers describe en Working Effectively with Legacy Code .

La idea básica es aprovechar el enlazador y tu sistema de compilación. Al compilar las pruebas unitarias, enlace en su propia versión de getaddrinfo que tendrá prioridad sobre la versión del sistema. Por ejemplo:

test.cpp:

#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> #include <iostream> int main(void) { int retval = getaddrinfo(NULL, NULL, NULL, NULL); std::cout << "RV:" << retval << std::endl; return retval; }

lib.cpp:

#include <sys/types.h> #include <sys/socket.h> #include <netdb.h> int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res ) { return 42; }

Y luego para probar:

$ g++ test.cpp lib.cpp -o test $ ./test RV:42