2011-03-24 17 views
11

Supongamos que tengo clases¿Simula objetos en C++ siempre requiere métodos o plantillas virtuales?

class Inner { 
    public: 
    void doSomething(); 
}; 

class Outer { 
    public: 
    Outer(Inner *inner); // Dependency injection. 

    void callInner(); 
}; 

pruebas unitarias adecuada dice que debería tener pruebas para Inner. Entonces, debería tener pruebas para Outer que no usan Inner real sino más bien MockInner para realizar pruebas unitarias en la funcionalidad agregada por solo Outer en lugar de la pila completa Outer/Inner.

Para ello, Googletest parece sugerir girando Inner en una clase abstracta pura (interfaz) de esta manera:

// Introduced merely for the sake of unit-testing. 
struct InnerInterface { 
    void doSomething() = 0; 
}; 

// Used in production. 
class Inner : public InnerInterface { 
    public: 
    /* override */ void doSomething(); 
}; 

// Used in unit-tests. 
class MockInner : public InnerInterface { 
    public: 
    /* override */ void doSomething(); 
}; 

class Outer { 
    public: 
    Outer(Inner *inner); // Dependency injection. 

    void callInner(); 
}; 

Por lo tanto, en el código de producción, me gustaría utilizar Outer(new Inner); mientras estaba en prueba, Outer(new MockInner).

OK. Parece bueno en teoría, pero cuando comencé a usar esta idea en todo el código, me encontré creando una clase abstracta pura para cada clase maldita. Es una gran cantidad de tipeo de placa de caldera, incluso si se puede ignorar el ligero rendimiento de tiempo de ejecución degradable debido al despacho virtual innecesario.

Un enfoque alternativo es utilizar plantillas como en el siguiente:

class Inner { 
    public: 
    void doSomething(); 
}; 

class MockInner { 
    public: 
    void doSomething(); 
}; 

template<class I> 
class Outer { 
    public: 
    Outer(I *inner); 

    void callInner(); 
}; 

// In production, use 
Outer<Inner> obj; 

// In test, use 
Outer<MockInner> test_obj; 

Esto evita la caldera de chapado y el envío virtual innecesaria; pero ahora toda mi base de código está en los voluminosos archivos de encabezado, lo que hace que sea imposible ocultar las implementaciones de origen (sin mencionar tratar con frustrantes errores de compilación de plantillas y el largo tiempo de compilación).

¿Son esos dos métodos, virtuales y plantillas, las únicas formas de hacer las pruebas unitarias correctas? ¿Hay mejores maneras de hacer las pruebas unitarias correctas?

Mediante pruebas unitarias correctas, quiero decir cada prueba unitaria prueba solo las funcionalidades introducidas por esa unidad pero no las dependencias de la unidad también.

Respuesta

5

No creo que debe burlarse de todas las dependencias de su clase probada en la práctica. Si es complicado crear, usar o detectar, entonces sí. Además, si depende directamente de algún recurso externo innecesario, como un DB, una red o un sistema de archivos.

Pero si ninguno de estos es un problema, IMO está bien solo usar una instancia de este directamente. Como ya lo ha probado la unidad, puede estar razonablemente seguro de que funciona como se espera y no interfiere con las pruebas unitarias de nivel superior.

Personalmente, prefiero las pruebas de la unidad de trabajo y el diseño simple, limpio y fácil de mantener, antes de adherir a una configuración ideal realizada por puristas de pruebas unitarios.

cada prueba unitaria prueba solo las funcionalidades introducidas por esa unidad pero no las dependencias de la unidad también.

El uso de una funcionalidad y probar una funcionalidad son dos cosas muy diferentes.

+0

Personalmente, prefiero las pruebas de la unidad de trabajo y el diseño simple, limpio y fácil de mantener, sobre la adherencia a una configuración ideal por parte de los puristas de pruebas unitarios. Me gusta esta respuesta --- Manteniendo las cosas prácticas. – kirakun

2

También creo que usar una instancia en Inner directamente está bien. Mi problema es burlarse de objetos externos que no son parte de mi código (proporcionado a través de bibliotecas estáticas o DLL, a veces de terceros). Me inclino a reescribir una DLL o biblioteca de falsa con los mismos nombres de clase, y luego vincular de manera diferente para la prueba. La modificación del archivo de encabezado de la dependencia externa para agregar "virtuales" me parece inaceptable. ¿Alguien tiene una mejor solución?