2008-11-23 34 views
13

¿Hay alguna forma de probar los destructores? Al igual que decir que tengo una clase como este ejemplo (artificial):Destructores de pruebas unitarias?

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 buena manera de probar la unidad para asegurarse de que esta x se elimina sin estorbar encima de mi archivo de HPP con #ifdef pruebas o romper 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.

Respuesta

7

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 simulado (o una fábrica simulada que crea objetos simulados) y el destructor registra el hecho de que ha sido llamado. La prueba falla si no es así.

Por supuesto, no se puede simular (ni 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.

Comprobación de fugas de memoria en las pruebas de unidad a menudo se puede hacer en un nivel superior, como otros han dicho. Pero eso sólo comprueba que un destructor se llama, que no prueba que el destructor derecho fue llamado.Por lo tanto, no lo haría, p. captura una declaración "virtual" que falta sobre el destructor del tipo del miembro x (de nuevo, no relevante si es solo un int).

+0

Precisamente lo que estaba diciendo –

+1

Sí, y casi exactamente al mismo tiempo.Supongo que gané porque te detuviste a escribir un ejemplo de código :-) –

0

Algunos compiladores sobrescribirá borrado de memoria con un patrón conocido en el modo de depuración para ayudar a detectar el acceso a la referencia colgante. Sé que Visual C++ solía usar 0xDD, pero no lo he usado desde hace tiempo.

En el caso de test, puede almacenar una copia de x, deje que se vaya fuera del alcance y asegúrese de que * x == 0xDDDDDDDD:

void testDestructor() 
{ 
    int *my_x; 
    { 
     X object; 
     my_x = object.getX(); 
    } 
    CPPUNIT_ASSERT(*my_x == 0xDDDDDDDD); 
} 
+0

Desprecio esta idea. No puede codificar la unidad de prueba con optimizaciones habilitadas o si su compilador cambia –

+0

Estoy de acuerdo en que no es bueno por las razones que ha mencionado, y no puede acceder a punteros privados. En general, creo que su idea (y la de Onebyone) es mejor, pero no siempre es trivial usar un objeto simulado. –

1

que tienden a ir con un "por cualquier medio necesario "enfoque para la prueba. 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 sé de ninguna otra manera de hacer esto en C++ que tener la llamada al destructor en un producto único para registrar que ha sido 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

0

no una sugerencia agnóstico plataforma, llamadas, pero en el pasado que he hecho en funciones de comprobación del montón de la CRT durante las pruebas unitarias, para verificar que no haya más memoria asignada al final de una prueba (o quizás un juego completo de pruebas) que el comienzo. También es posible que pueda hacer algo similar con la instrumentación de su plataforma, verificar el conteo de manipuladores, etc.

1

En el ejemplo, defina e instrumente su propio global nuevo y elimínelo.

Para evitar #ifdefs, he hecho que las clases de prueba sean amigos. Puede establecer/guardar/obtener el estado según sea necesario para verificar los resultados de una llamada.

+0

Esta idea no tiene escala. ¿Qué pasa si necesita probar muchas clases diferentes, va a implementar diferentes versiones de su nuevo global y eliminar? ¿Qué pasa si es un código de prueba unitaria que ya reemplaza a :: new y :: delete? –

+0

No sé qué hombre de paja imagina. La instrumentación está destinada a responder preguntas como "¿x todavía está asignado?" Otras respuestas aquí aprovechan una versión específica de la plataforma de la instrumentación de memoria en lugar de tomar el control usted mismo. –

+0

Su diseño es pobre. ¿Por qué deberías modificar tus clases bajo prueba para que tu arnés de prueba pueda funcionar con él? Si haces que tus clases sean probables desde el principio (mira mi ejemplo), no necesitas recurrir a tales cosas –

5

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, la X constructor tiene que pedir el objeto que implementa la interfaz int:

template<class T> 
class X 
{ 
    T *x; 
    public: 
    X(T* inx) 
    : x(inx) 
    { 
    } 

    // etc 
}; 

ahora se convierte en fácil de burlar en el valor de x, y la maqueta puede manejar la comprobación de la destrucción correcta.

No preste atención a las personas que dicen que debe romper el encapsulamiento o recurrir a hacks 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.

+0

Es la única manera de hacerlo para un int, pero una plantilla de clase no es un reemplazo trivial para una clase. El uso de plantillas en todas partes generalmente limita sus opciones de archivos DLL, lo que reduce la dependencia del encabezado, etc. Todo depende del compilador, por supuesto: me han dicho que algunos admiten una mejor vinculación de plantillas que otros. –

+0

Eso sí, tal vez estoy solo de mal humor porque dije que no se puede burlar de un tipo integrado, que como ha señalado es falso, porque puede hacerlo de esta manera a través de plantillas y una clase que implementa el conjunto int interface (no trivial, pero solo tienes que hacerlo una vez. No olvide numeric_limits). –

+0

Bueno, no tendrías que hacerlo templatado. Si eliminaste la plantilla y cambiaste T a int en todas partes en mi ejemplo, aún así se podría probar –

1

No será relevante para el tipo que hizo la pregunta, pero podría ser útil para los demás que la lean. Me hicieron una pregunta similar en una entrevista de trabajo.

Suponiendo que la memoria está limitado, puede probar este método:

  1. asignar memoria hasta que la asignación de un error con el mensaje Sin memoria (antes de ejecutar cualquier prueba relevante para el destructor) y guardar el tamaño de la memoria disponible antes de ejecutar la prueba.
  2. Ejecute la prueba (llame al constructor y realice algunas acciones como desee en la nueva instancia).
  3. Ejecute el destructor.
  4. Ejecute de nuevo la parte de asignación (como en el paso 1) si puede asignar exactamente la misma memoria que logra asignar antes de ejecutar la prueba, el destructor funciona bien.

Este método funciona (razonablemente) cuando tiene poca memoria limitada, de lo contrario parece irrazonable, al menos para mi opinión.