c++ - test - Destructores de pruebas unitarias?
pruebas unitarias java (7)
¿Hay alguna forma de probar los destructores? Como decir que tengo una clase como esta (artificial) ejemplo:
class X
{
private:
int *x;
public:
X()
{
x = new int;
}
~X()
{
delete x;
}
int *getX() {return x;}
const int *getX() const {return x;}
};
¿Hay alguna manera de probar esta unidad para asegurarse de que x se borre sin saturar mi archivo hpp con #ifdef TEST o rompiendo la encapsulación? El principal problema que estoy viendo es que es difícil saber si x realmente se eliminó, especialmente porque el objeto está fuera de alcance en el momento en que se llama al destructor.
Algunos compiladores sobrescribirán la memoria eliminada con un patrón conocido en modo de depuración para ayudar a detectar el acceso a punteros colgantes. Sé que Visual C ++ solía usar 0xDD, pero no lo he usado desde hace tiempo.
En su caso de prueba, puede almacenar una copia de x, dejarla fuera del alcance y asegurarse de que * x == 0xDDDDDDDD:
void testDestructor()
{
int *my_x;
{
X object;
my_x = object.getX();
}
CPPUNIT_ASSERT( *my_x == 0xDDDDDDDD );
}
Creo que su problema es que su ejemplo actual no es comprobable. Como desea saber si se eliminó x
, realmente necesita poder reemplazar x
con un simulacro. Esto es probablemente un poco OTTO para un int pero supongo que en tu ejemplo real tienes alguna otra clase. Para que sea comprobable, el constructor de X
necesita solicitar el objeto que implementa la interfaz int
:
template<class T>
class X
{
T *x;
public:
X(T* inx)
: x(inx)
{
}
// etc
};
Ahora es simple simular el valor de x
, y el simulacro puede controlar la destrucción correcta.
Por favor, no preste atención a las personas que dicen que debe romper el encapsulamiento o recurrir a pirateos horribles para obtener un código comprobable. Si bien es cierto que el código probado es mejor que el código no probado, el código comprobable es el mejor de todos y siempre da como resultado un código más claro con menos ataques y un menor acoplamiento.
En el ejemplo, defina e instrumente su propio global nuevo y elimínelo.
Para evitar #ifdefs, hago que las clases de prueba sean amigos. Puede establecer / guardar / obtener el estado según sea necesario para verificar los resultados de una llamada.
No es una sugerencia agnóstica de la plataforma, pero en el pasado hice llamadas a las funciones de comprobación de pila del CRT durante la prueba unitaria, para verificar que no haya más memoria asignada al final de una prueba (o tal vez un conjunto completo de pruebas) que la comienzo. También es posible que pueda hacer algo similar con la instrumentación de su plataforma, verificar el conteo de controladores, etc.
Puede haber algo que decir acerca de la inyección de dependencia. En lugar de crear un objeto (en este caso un int, pero en un caso no artificial más probablemente un tipo definido por el usuario) en su constructor, el objeto se pasa como un parámetro al constructor. Si el objeto se crea más tarde, se pasa una fábrica al constructor de X.
Luego, cuando está probando la unidad, pasa un objeto falso (o una fábrica simulada que crea objetos falsos), y el destructor registra el hecho de que ha sido llamado. La prueba falla si no es así.
Por supuesto, no puede simular (o reemplazar) un tipo incorporado, por lo que en este caso particular no es bueno, pero si define el objeto / fábrica con una interfaz, entonces puede hacerlo.
La comprobación de fugas de memoria en las pruebas unitarias a menudo puede hacerse en un nivel superior, como han dicho otros. Pero eso solo comprueba que se llamó a un destructor, no prueba que se llamó al destructor correcto . Por lo tanto, no capturaría, por ejemplo, una declaración "virtual" faltante en el destructor del tipo del miembro x (de nuevo, no relevante si es solo un int).
Tiendo a ir con un enfoque de "Por cualquier medio necesario" para las pruebas. Si necesita una prueba, estoy dispuesto a filtrar abstracciones, romper la encapsulación y hackear ... porque el código probado es mejor que el código bonito. A menudo nombraré los métodos que rompen esto algo como VaildateForTesting o OverrideForTesting para dejar en claro que la violación de la encapsulación está destinada solo a pruebas.
No conozco otra forma de hacer esto en C ++ que tener la llamada de destructor en un singleton para registrar que se ha destruido. He encontrado un método para hacer algo similar a esto en C # usando una referencia débil (no violo la encapsulación o las abstracciones con este enfoque). No soy lo suficientemente creativo como para proponer una analogía con C ++, pero PUEDES serlo. Si ayuda, genial, si no, lo siento.
http://houseofbilz.com/archive/2008/11/11/writing-tests-to-catch-memory-leaks-in-.net.aspx
No será relevante para el tipo que hizo la pregunta, pero podría ser útil para otros que lo lean. Me hicieron una pregunta similar en una entrevista de trabajo.
Suponiendo que la memoria es limitada, puede probar este método:
- Asigne memoria hasta que la asignación falle con un mensaje de falta de memoria (antes de ejecutar cualquier prueba relevante para el destructor) y guarde el tamaño de la memoria disponible antes de ejecutar la prueba.
- Ejecute la prueba (llame al constructor y haga algunas acciones como desee en la nueva instancia).
- Ejecuta el destructor.
- Ejecute de nuevo la parte de asignación (como en el paso 1) si puede asignar exactamente la misma memoria que administra asignar antes de ejecutar la prueba, el destructor funciona bien.
Este método funciona (razonablemente) cuando tienes poca memoria limitada, de lo contrario parece irracional, al menos para mi opinión.