2010-11-25 21 views
20

Se sabe que en C++ burlarse/fingir métodos no virtuales para la prueba es difícil. Por ejemplo, cookbook of googlemock tiene dos sugerencias: ambas significan modificar el código fuente original de alguna manera (creación de plantillas y reescritura como interfaz).falso/simulacro de métodos no virtuales C++

Parece que este es un problema muy grave para el código C++. ¿Cómo se puede hacer mejor si no puede modificar el código original que necesita ser fingido/burlado? Duplicar todo el código/clase (con él toda la jerarquía de la clase base?)

+0

Existen algunas técnicas que manejan métodos no virutales. Consulta https://github.com/google/googletest/blob/master/googlemock/docs/CookBook.md#mocking-nonvirtual-methods –

Respuesta

-2

Solía ​​crear una interfaz para las partes que necesitaba para simular. Luego, simplemente creé una clase de código auxiliar que derivaba de esta interfaz y pasé esta instancia a mis clases bajo prueba. Sí, es un trabajo arduo, pero creo que vale la pena en algunas circunstancias.

Oh, por interfaz me refiero a struct con solo puros métodos virtuales. ¡Nada más!

+0

No lo entiendo. Tengo una clase concreta que quiero fingir, y no puedo cambiar su código. ¿Cómo puede simular esta clase con el método que propone? – zaharpopov

+0

Tendrá que extraer su interfaz pública. Entonces, el código que tiene bajo prueba necesitará usar esta interfaz en lugar de su clase concreta. Esto le permitirá cambiar la implementación con su stub/mock. –

+0

¿Entonces dices que debería cambiar el código original? – zaharpopov

9

Una forma que a veces utilizamos es dividir el archivo original .cpp en al menos dos partes.

Luego, el aparato de prueba puede suministrar sus propias implementaciones; utilizando efectivamente el enlazador para hacer el trabajo sucio para nosotros.

Esto se denomina "Link Seam" en algunos círculos.

+0

horrible duplicación de código. ¡Piensa si la clase tiene clases base! – zaharpopov

+0

@zaharpopov duplica las interfaces, pero no todo el contenido de la clase. Porque, presumiblemente, la versión de prueba del código tiene "agallas" completamente diferentes. – sdg

+0

++ para el enlace al capítulo del libro - muy interesante – zaharpopov

-2

Eso es más fácil de lo que crees. Simplemente pase el objeto construido al constructor de la clase que está probando. En la clase, almacene la referencia a ese objeto. Entonces es fácil usar clases simuladas.

EDIT:

El objeto que está pasando al constructor necesita una interfaz, y que sólo el almacenamiento de clases referencia a la interfaz.


struct Abase 
{ 
    virtual ~Abase(){} 
    virtual void foo() = 0; 
}; 

struct Aimp : public Abase 
{ 
    virtual ~Aimp(){} 
    virtual void foo(){/*do stuff*/} 
}; 

struct B 
{ 
    B(Aimp &objA) : obja(objA) 
    { 
    } 

    void boo() 
    { 
    objA.foo(); 
    } 

    Aimp &obja; 
}; 


int main() 
{ 
//... 
Aimp realObjA; 
B objB(realObjA); 
// ... 
} 

En la prueba, se puede pasar el objeto de burla fácil.

+0

lo siento, completamente no entiendo lo que dijo. ¿Puedes explicar más y dar ejemplo? – zaharpopov

+0

por lo que dices, "¿modificar el código bajo prueba, para que sea comprobable"? –

+0

@Steve No, estoy diciendo "practica el TDD", y tu código será probado. –

7

El código debe estar escrito para ser comprobado, cualquiera sea la técnica de prueba que utilice. Si quieres probar usando simulacros, eso significa alguna forma de inyección de dependencia.

llamadas no virtual sin dependencia de un parámetro de plantilla plantean el mismo problema que final y static métodos en Java [*] - el código bajo prueba ha dicho explícitamente, "Quiero llamar a este código, no una un bit de código desconocido que depende de algún modo de un argumento ". Usted, el probador, quiere que invoque un código diferente bajo prueba de lo que normalmente llama. Si no puede cambiar el código bajo prueba, usted, el probador, perderá ese argumento. También podría preguntar cómo introducir una versión de prueba de la línea 4 de una función de 10 líneas sin cambiar el código bajo prueba.

Si la clase a la que se debe burlar está en una TU diferente de la clase bajo prueba, puede escribir una simulación con el mismo nombre que el original y vincularla en su lugar. Ya sea que puedas generar ese simulacro usando tu marco burlón de la manera normal, no estoy tan seguro.

Si lo desea, supongo que es un "muy mal problema para C++" que es posible escribir código que es difícil de probar. Comparte este "problema" con una gran cantidad de otros lenguajes ...

[*] Mi conocimiento de Java es bastante bajo.Puede haber alguna manera ingeniosa de burlarse de tales métodos en Java, que no son aplicables a C++. Si es así, ignórelos para ver la analogía ;-)

+0

suena triste, pero es cierto lo que dices aquí, pero supongamos que tengo código heredado, por lo que no hay absolutamente ** forma ** de burlarse de él? – zaharpopov

+0

@zaharpopov: entonces tendrás que probarlo de otra forma. ¿Has considerado pruebas funcionales/de aceptación? ;-) –

0

Usted dice muy específicamente "si no puede modificar el código original", que las técnicas que menciona en su pregunta (y todas las demás respuestas actuales) ") hacer.

Sin cambiar esa fuente, aún en general (para OS/herramientas comunes) precarga un objeto que define su propia versión de la (s) función (es) que desea interceptar. Incluso pueden llamar a las funciones originales después. Proporcioné un ejemplo de hacer esto en (mi) pregunta Good Linux TCP/IP monitoring tools that don't need root access?.

+0

desafortunadamente mi código no es C – zaharpopov

+0

¿Y qué? También funcionará para C++. En la etapa del enlazador, es todo lo mismo. –

+0

No es mucho más difícil en C++ porque mangling nombre - recuerde que esta no es una función simple cargada con carga dinámica, pero toda una clase con clases base – zaharpopov

7

Seguí el enlace Link Seam desde sdg's answer. Allí leí sobre diferentes tipos de costuras, pero me impresionaron mucho las costuras de preprocesamiento. Esto me hizo pensar en explotar aún más el preprocesador. Resultó que es posible simular cualquier dependencia externa sin cambiar realmente el código de llamada.

Para hacer esto, debe compilar el archivo de origen de la llamada con una definición de dependencia sustituta. Aquí hay un ejemplo de cómo hacerlo.

dependency.h

#ifndef DEPENDENCY_H 
#define DEPENDENCY_H 

class Dependency 
{ 
public: 
    //... 
    int foo(); 
    //... 
}; 

#endif // DEPENDENCY_H 

caller.cpp

#include "dependency.h" 

int bar(Dependency& dependency) 
{ 
    return dependency.foo() * 2; 
} 

test.cpp

#include <assert.h> 

// block original definition 
#define DEPENDENCY_H 

// substitute definition 
class Dependency 
{ 
public: 
    int foo() { return 21; } 
}; 

// include code under test 
#include "caller.cpp" 

// the test 
void test_bar() 
{ 
    Dependency mockDependency; 

    int r = bar(mockDependency); 

    assert(r == 42); 
} 

en cuenta que la maqueta no tiene que poner en práctica completa Dependency, sólo el mínimo (utilizado por caller.cpp) para que la prueba pueda compilarse y ejecutarse. De esta manera puede simular funciones globales, estáticas, no virtuales o casi cualquier dependencia sin cambiar el código de producción. Otra razón por la que me gusta este enfoque es que todo lo relacionado con la prueba está en un solo lugar. No tiene que ajustar configuraciones de compilador y enlazador aquí y allá.

He aplicado esta técnica con éxito en un proyecto del mundo real con grandes dependencias de grasa. Lo he descrito con más detalle en Include mock.

+1

El problema con este método es que ya no puede probar la clase original "Dependencia" en el mismo proyecto. – MyUsername112358

+1

Puede probarlo en otra unidad de compilación, p. en test2.cpp –

1

@zaharpopov puede usar Typemock IsolatorPP para crear simulaciones de clases y métodos no virtuales sin cambiar su código (o código heredado). por ejemplo, si tienen una clase no virtual llamado MyClass:

class MyClass 
{ 
public: 
    int GetResult() { return -1; } 
} 

que puede burlarse con typemock así:

MyClass* fakeMyClass = FAKE<MyClass>(); 
WHEN_CALLED(fakeMyClass->GetResult()).Return(10); 

Por cierto las clases o métodos que desea probar puede también será privada como typemock puede burlarse de ellos también, por ejemplo:

class MyClass 
{ 
private: 
    int PrivateMethod() { return -1; } 
} 


MyClass* myClass = new MyClass(); 

PRIVATE_WHEN_CALLED(myClass, PrivateMethod).Return(1); 

para obtener más información, visite here.

1

Creo que no es posible hacerlo con C++ estándar en este momento (pero esperemos que pronto llegue a C++ una poderosa reflexión en tiempo de compilación ...). Sin embargo, hay varias opciones para hacerlo.

Puedes echar un vistazo a Injector++. Solo es Windows en este momento, pero planea agregar soporte para Linux & Mac.

Otra opción es CppFreeMock, que parece funcionar con GCC, pero no tiene actividades recientes.

HippoMocks también ofrecen dicha capacidad, pero solo para funciones gratuitas. No es compatible con las funciones de los miembros de la clase.

No estoy del todo seguro, pero parece que todo lo anterior logra esto sobrescribiendo la función objetivo en el tiempo de ejecución para que salte a la función simulada.

Existe C-Mock, que es una extensión de Google Mock que le permite simular funciones no virtuales redefiniéndolas y confiando en que las funciones originales se encuentran en bibliotecas dinámicas. Está limitado a la plataforma GNU/Linux.

Finalmente, también puedes probar PowerFake (para el cual, soy el autor) como se introdujo here.

No es un marco de burla (actualmente) y ofrece la posibilidad de reemplazar las funciones de producción con las de prueba. Espero ser capaz de integrarlo a uno o más marcos de burla; si no, se convertirá en uno.

También anula la función original durante la vinculación (por lo tanto, no funcionará si se llama a una función en la misma unidad de traducción en la que está definida), pero utiliza un truco diferente que C-Mock ya que usa GNU ld es --wrap opción. También necesita algunos cambios en su sistema de compilación para las pruebas, pero no afecta el código principal de ninguna manera (excepto si se ve obligado a poner una función en un archivo .cpp separado); pero se proporciona soporte para integrarlo fácilmente en los proyectos de CMake.

Pero, actualmente está limitado a GCC/GNU ld (funciona también con MinGW).

Cuestiones relacionadas