una programación privada orientada objetos iniciar estructura empleado ejemplos declaracion como clases clase c++ unit-testing testing

programación - Prueba unitaria c++. ¿Cómo probar a los miembros privados?



programación orientada a objetos en c++ (8)

Me gustaría hacer pruebas unitarias para mi aplicación C ++.

¿Cuál es la forma correcta de evaluar a los miembros privados de una clase? Haga una clase de amigo que pondrá a prueba los miembros privados, utilizar una clase derivada, o algún otro truco?

¿Qué técnica usan las API de prueba?


A pesar de los comentarios sobre la conveniencia de probar métodos privados, supongamos que realmente lo necesita ... este es a menudo el caso, por ejemplo, cuando se trabaja con código heredado antes de refaccionarlo en algo más apropiado. Este es el patrón que he usado:

// In testable.hpp: #if defined UNIT_TESTING # define ACCESSIBLE_FROM_TESTS : public # define CONCRETE virtual #else # define ACCESSIBLE_FROM_TESTS # define CONCRETE #endif

Luego, dentro del código:

#include "testable.hpp" class MyClass { ... private ACCESSIBLE_FROM_TESTS: int someTestablePrivateMethod(int param); private: // Stuff we don''t want the unit tests to see... int someNonTestablePrivateMethod(); class Impl; boost::scoped_ptr<Impl> _impl; }

¿Es mejor que definir amigos de prueba? Parece menos detallado que la alternativa, y está claro en el encabezado lo que está sucediendo. Ambas soluciones no tienen nada que ver con la seguridad: si realmente está preocupado por los métodos o miembros, estos deben estar ocultos dentro de una implementación opaca posiblemente con otras protecciones.


A veces, se requiere probar métodos privados. Las pruebas se pueden hacer agregando FRIEND_TEST a la clase.

// Production code // prod.h #include "gtest/gtest_prod.h" ... class ProdCode { private: FRIEND_TEST(ProdTest, IsFooReturnZero); int Foo(void* x); }; //Test.cpp // TestCode ... TEST(ProdTest, IsFooReturnZero) { ProdCode ProdObj; EXPECT_EQ(0, ProdObj.Foo(NULL)); //Testing private member function Foo() }


El deseo de poner a prueba a los miembros privados es un olor de diseño, lo que generalmente indica que hay una clase atrapada dentro de su clase que lucha por salir. Toda la funcionalidad de una clase debe poder ejercitarse a través de sus métodos públicos; la funcionalidad a la que no se puede acceder públicamente en realidad no existe.

Hay un par de enfoques para darse cuenta de que debe probar que sus métodos privados hacen lo que dicen en la lata. Las clases de amigos son las peores de estas; vinculan la prueba a la implementación de la clase bajo prueba de una manera frágil prima facie. Algo mejor es la inyección de dependencia: hacer que los atributos de la clase de dependencias de los métodos privados que la prueba puede proporcionar versiones falsificadas de modo que permita la prueba de métodos privados a través de la interfaz pública. Lo mejor es extraer una clase que encapsula el comportamiento que tienen sus métodos privados como su interfaz pública, y luego probar la nueva clase como lo haría normalmente.

Para más detalles, consulte Clean Code .


Hay una solución simple en C ++ usando #define. Simplemente envuelva la inclusión de su "ClassUnderTest" de esta manera:

#define protected public #define private public #include <ClassUnderTest.hpp> #undef protected #undef private

[El crédito va a este artículo y RonFox] [1]


No he encontrado una solución dorada, pero puede usar un friend para probar miembros privados, si sabe cómo el framework de pruebas nombra sus métodos. Utilizo lo siguiente para probar a los miembros privados con la prueba de Google. Si bien esto funciona bastante bien, tenga en cuenta que es un truco, y no lo uso en el código de producción.

En el encabezado del código que deseo probar (stylesheet.h), tengo:

#ifndef TEST_FRIENDS #define TEST_FRIENDS #endif class Stylesheet { TEST_FRIENDS; public: // ... private: // ... };

y en la prueba tengo:

#include <gtest/gtest.h> #define TEST_FRIENDS / friend class StylesheetTest_ParseSingleClause_Test; / friend class StylesheetTest_ParseMultipleClauses_Test; #include "stylesheet.h" TEST(StylesheetTest, ParseSingleClause) { // can use private members of class Stylesheet here. }

Siempre agrega una nueva línea a TEST_FRIENDS si agrega una nueva prueba que accede a miembros privados. Los beneficios de esta técnica son que es bastante discreto en el código probado, ya que solo agrega algunas # definiciones, que no tienen ningún efecto cuando no se prueban. El inconveniente es que es un poco detallado en las pruebas.

Ahora una palabra sobre por qué querrías hacer esto. Idealmente, por supuesto, tiene clases pequeñas con responsabilidades bien definidas, y las clases tienen interfaces fácilmente comprobables. Sin embargo, en la práctica eso no siempre es fácil. Si está escribiendo una biblioteca, lo que es private y public está dictado por lo que quiere que el consumidor de la biblioteca pueda usar (su API pública) y no por lo que necesite o no. Puede tener invariantes que es muy poco probable que cambien, y deben ser probados, pero no son de interés para el consumidor de su API. Entonces, las pruebas de caja negra de la API no son suficientes. Además, si encuentra errores y escribe pruebas adicionales para evitar regresiones, puede ser necesario probar cosas private .


Preferiría agregar -Dprivate = public option en Makefile of unit-test, evitando modificar algo en mis proyectos originales


Responder a esta pregunta toca muchos otros temas. Además de cualquier religiosidad en CleanCode, TDD y otros:

Hay varias formas de acceder a miembros privados. ¡En cualquier caso, debe anular el código probado! Esto es posible en ambos niveles de análisis C ++ (preprocesador y lenguaje en sí):

Definir todo para público

Al usar el preprocesador, puede romper la encapsulación.

#define private public #define protected public #define class struct

¡La desventaja es que la clase del código entregado no es la misma que en la prueba ! El estándar de C ++ en el capítulo 9.2.13 dice:

El orden de asignación de los miembros de datos no estáticos con diferente control de acceso no está especificado.

Esto significa que el compilador tiene derecho a reordenar las variables miembro y las funciones virtuales para la prueba. Puede tener problemas, esto no dañará sus clases si no ocurre un desbordamiento de búfer, pero significa que no probará el mismo código que entrega. Significa que si accede a miembros de un objeto, que fue inicializado por código, compilado con private no definido como public , ¡la compensación de su miembro puede diferir!

Amigos

Este método necesita cambiar la clase probada para entablar amistad con la clase de prueba o la función de prueba. Algunos frameworks de prueba como gtest ( FRIEND_TEST(..); ) tienen una funcionalidad especial para soportar esta forma de acceder a cosas privadas.

class X { private: friend class Test_X; };

Abre la clase solo para la prueba y no abre el mundo, pero debe modificar el código que se entrega. En mi opinión, esto es algo malo, porque una prueba nunca debería cambiar el código probado. Como una desventaja adicional, le da a las otras clases del código entregado la posibilidad de entrometerse en su clase llamándose a sí mismos como una clase de prueba (esto también dañaría la regla ODR del Estándar C ++).

Declarar las cosas privadas protegidas y derivar de la clase para las pruebas

No es una forma muy elegante, muy intrusiva, pero también funciona:

class X { protected: int myPrivate; }; class Test_X: public X { // Now you can access the myPrivate member. };

De otra forma con macros

Funciona, pero tiene las mismas desventajas en la conformidad estándar como la primera manera. p.ej:

class X { #ifndef UNITTEST private: #endif };

Creo que las últimas dos formas no son alternativas a las dos primeras formas, porque no tienen ventajas sobre las primeras, pero son más intrusivas en el código probado. La primera forma es muy arriesgada, por lo que puedes usar el enfoque de amistad.

Algunas palabras en la discusión nunca-prueba-privada-cosas. Una de las ventajas de las pruebas unitarias es que llegará muy pronto al punto en el que debe mejorar el diseño de su código. Este también es a veces uno de los inconvenientes de las pruebas unitarias. Hace que la orientación del objeto a veces sea más complicada de lo que debe ser. Especialmente si sigues la regla para diseñar clases de la misma manera que los objetos del mundo real.

Luego, a veces tiene que cambiar el código en algo desagradable, porque el enfoque de prueba de unidad lo obliga a hacerlo. Trabajar en frameworks complejos, que se usan para controlar procesos físicos, es un ejemplo. Ahí desea mapear el código en el proceso físico, porque a menudo partes del proceso ya son muy complejas. La lista de dependencia en esos procesos a veces es muy larga. Este es un momento posible, donde probar a los miembros privados se está volviendo agradable. Tienes que negociar con las ventajas y desventajas de cada enfoque.

¡Las clases se vuelven a veces complejas! Luego debes decidir dividirlos o tomarlos tal como están. A veces, la segunda decisión tiene más sentido. Al final, siempre es una cuestión de los objetivos que desea lograr (por ejemplo, diseño perfecto, tiempos de incorporación rápidos, bajos costos de desarrollo ...).

Mi opinión

Mi proceso de decisión para acceder a miembros privados se ve así:

  1. ¿Necesita probar miembros privados? (A menudo, esto reduce la cantidad total de pruebas necesarias)
  2. En caso afirmativo, ¿ve alguna ventaja de diseño para refactorizar la clase?
  3. Si no, hazte amigo de la prueba en tu clase (úsala por las alternativas que faltan).

No me gusta el enfoque de amistad, porque cambia el código probado, pero el riesgo de probar algo, que puede no ser el mismo que se entregó (como es posible con el primer enfoque), no justificará el código del limpiador.

Por cierto: probar solo la interfaz pública también es una cuestión fluida, porque según mi experiencia, cambia tan a menudo como lo hace la implementación privada. Entonces, no tiene ninguna ventaja para reducir la prueba en miembros públicos.


Típicamente, uno solo prueba la interfaz pública como se discutió en los comentarios de la pregunta.

Sin embargo, en ocasiones es útil probar métodos privados o protegidos. Por ejemplo, la implementación puede tener algunas complejidades no triviales que están ocultas a los usuarios y que se pueden probar de manera más precisa con el acceso a miembros no públicos. A menudo es mejor descubrir una forma de eliminar esa complejidad o averiguar cómo exponer públicamente las partes relevantes, pero no siempre.

Una forma de permitir que las pruebas unitarias accedan a miembros no públicos es a través de la construcción de friend .