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.
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