Pruebas unitarias de métodos privados en C++

10 minutos de lectura

Avatar de usuario de Mumbles
murmura

Estoy en el proceso de escribir algunas pruebas unitarias. En particular, quiero probar algunos métodos privados.

Hasta ahora, he llegado a usar.

#define private public

Pero no estoy contento con esto, ya que destruirá toda la encapsulación desde el punto de vista de la prueba unitaria.

¿Qué métodos utiliza para realizar pruebas unitarias de métodos privados?

  • ¿Qué tipo de marco de pruebas unitarias de C++ está utilizando?

    – Laurens Ruijtenberg

    9 de septiembre de 2010 a las 12:52

  • Tenga en cuenta que hay formas de usar miembros privados en C++. Puedes leer sobre ello en mi blog: bloglitb.blogspot.com/2010/07/…

    – Johannes Schaub – litb

    9 de septiembre de 2010 a las 21:36

  • #define private public – es ilegal definir una palabra reservada.

    – J Bentley

    16 de marzo de 2013 a las 1:37

  • Esto no es un duplicado. Esta pregunta es sobre C++ (que tiene friend) mientras que la otra pregunta no es específica. Su respuesta principal es para Java, lo que sugiere una reflexión.

    – Qwertie

    23 de noviembre de 2018 a las 2:07


  • (1) Nunca ajusto el código a las necesidades de la prueba. (2) Es muy útil para probar métodos privados, especialmente en las primeras etapas de la escritura de código, cuando esos métodos son complicados matemáticamente (una fórmula matemática de unos 1800 caracteres). Probar los métodos públicos de alto nivel es una prueba posterior. Entonces #define private public antes #include y #undef private después, funciona bien para mí, pero tampoco me gusta.

    – Camaleón

    4 de mayo de 2022 a las 8:26


En lugar de lo desagradable #define truco que mencionas en la pregunta, un mecanismo más limpio es hacer que la prueba sea un amigo de la clase bajo prueba. Esto permite que el código de prueba (y solo el código de prueba) acceda a los privados, mientras los protege de todo lo demás.

Sin embargo, es preferible probar a través de la interfaz pública. Si su clase X tiene mucho código en las funciones de miembros privados, entonces podría valer la pena extraer una nueva clase Y que se usa para la implementación de la clase X. Esta nueva clase Y se puede probar a través de su interfaz pública, sin exponer su uso a los clientes de clase X.

  • Lo que se debe hacer es usar una definición de compilador para pruebas unitarias y #ifdef UNIT_TESTING friend void UnitTestFcn() #endif

    – iheanyi

    21 de abril de 2014 a las 15:25

  • Sí, puedes hacer la prueba condicionalmente a un amigo. Todavía es preferible probar a través de la interfaz pública.

    –Anthony Williams

    22 de abril de 2014 a las 8:30

  • Entonces haces públicos los métodos de una forma u otra 🙁

    – CPILL

    13 de julio de 2017 a las 6:59

  • Sí, pruebe los métodos públicos, pero cambie la clase en la que son públicos. Si la nueva clase Y es nombrado lib_namespace::detail::X_private_impl entonces es poco probable que lo usen usuarios ocasionales de X.

    –Anthony Williams

    31 de julio de 2017 a las 9:43

  • “Si su clase X tiene mucho código en las funciones de miembros privados, entonces podría valer la pena extraer una nueva clase Y que se utiliza para la implementación de la clase X”. A costa de inflar el espacio de nombres y aumentar el costo de comprender el código.

    – quant_dev

    19 de agosto de 2017 a las 18:01

avatar de usuario de jlstrecker
jlstrecker

Si está usando Google Test, puede usar AMIGO_TEST para declarar fácilmente su dispositivo de prueba como amigo de la clase bajo prueba.

Y ya sabes, si probar funciones privadas fuera inequívocamente malo como decían algunas de las otras respuestas, entonces probablemente no estaría integrado en Google Test.

Puede leer más sobre cuándo probar funciones privadas es bueno o malo en esta respuesta.

  • Este es claramente un ejemplo de apelar a la autoridad pero… ¡qué autoridad es Google Test! La mejor respuesta.

    – Olorín

    31 de agosto de 2016 a las 21:01

  • Los documentos de prueba de Google a los que se vincula sugieren encarecidamente rediseñar en lugar de probar métodos privados.

    – cevaris

    18 de diciembre de 2016 a las 14:56

  • Sí, considere rediseñar (vea la respuesta a la que me vinculé en mi publicación), pero “si tiene que probar absolutamente el código de la interfaz no pública, puede hacerlo”.

    – jlstrecker

    20 de diciembre de 2016 a las 3:35

Si los métodos son lo suficientemente complejos como para justificar la prueba de forma aislada, refactorícelos en su(s) propia(s) clase(s) y pruébelos a través de su(s) interfaz(es) pública(s). Luego úsalos de forma privada en la clase original.

  • ¿eh? ¿Por qué demonios las funciones públicas merecen más pruebas que las privadas?

    -Assaf Lavie

    9 de septiembre de 2010 a las 13:02

  • No entiendo por qué querer probar un método privado es un olor de diseño. Digamos que empiezo a escribir una clase y, al mismo tiempo, escribo la prueba unitaria, antes de comenzar a escribir la función pública, escribo algunas funciones privadas bastante simples que quiero probar que funcionan antes de comenzar a implementar las funciones públicas.

    – murmura

    9 de septiembre de 2010 a las 13:19

  • @Assaf: las funciones privadas no se pueden probar de forma no intrusiva, por lo que si necesitan pruebas de forma aislada, debe hacerlas públicas (donde están, o en otra clase) o agregar un mecanismo de prueba intrusivo. Mi sugerencia es refactorizar para que todo el código pueda probarse sin intrusiones; en mi opinión (que no todos compartirán), esto también brinda un diseño más limpio, al reducir las responsabilidades de la clase original y permitir la reutilización de lo que anteriormente era una funcionalidad privada. Use la sugerencia de Anthony de una clase de prueba de amigos si prefiere clases grandes con múltiples responsabilidades.

    –Mike Seymour

    9 de septiembre de 2010 a las 13:23

  • @David: es un olor porque indica que la clase tiene múltiples responsabilidades (implementación de su interfaz pública y también implementación de funcionalidad privada). Puede ser un diseño más limpio delegar la funcionalidad privada a otras clases, en lugar de métodos privados.

    –Mike Seymour

    9 de septiembre de 2010 a las 13:25


  • @Assaf: mirándolo de otra manera, creo que no debería ser necesario eliminar las pruebas si cambia los detalles de implementación de la clase sin cambiar su interfaz pública (o protegida, si lo desea). Tu podrías querer agregar pruebas después de una reescritura, cuando está realizando pruebas de caja blanca, para detectar nuevos casos “riesgosos”. No debería tener que eliminarlos: se supone que la nueva implementación debe hacer todo lo importante que hizo la anterior. Pero las funciones privadas son detalles de implementación. Las pruebas que dejan de funcionar cuando cambia un detalle de la implementación, aunque en realidad no se rompa nada, son molestias.

    –Steve Jessop

    9 de septiembre de 2010 a las 13:47


Avatar de usuario de Manoj R
manoj r

Haga la clase de prueba como amigo de la clase original. Esta declaración de amistad estará dentro del #define UNIT_TEST bandera.

class To_test_class {
   #ifdef UNIT_TEST
     friend test_class;
   #endif
}

Ahora, para su prueba unitaria, compilará el código con flag -DUNIT_TEST. De esta manera podrá probar la función privada.

Ahora su código de prueba de unidad no será enviado al entorno de producción, ya que UNIT_TEST la bandera será falsa. Por lo tanto, el código sigue siendo seguro.

Además, no necesitará ninguna biblioteca especial para las pruebas unitarias.

Sé que esta es una pregunta anterior, pero parece que nadie ha compartido el método relativamente bueno que prefiero, así que aquí va:

Cambie el método desde el que desea probar private a protected. Para otras clases, el método seguirá siendo privatepero ahora puede derivar una clase de “prueba” de su clase base que expone la funcionalidad privada que desea probar.

He aquí un ejemplo mínimo:

class BASE_CLASS {
  protected:
    int your_method(int a, int b);
};

class TEST_CLASS : public BASE_CLASS {
  public:
    int your_method(int a, int b) {
      return BASE_CLASS::your_method(a, b);
    }
}

Por supuesto, tendrá que actualizar sus pruebas unitarias para ejecutar sus pruebas en la clase derivada en lugar de la clase base, pero después de eso, cualquier cambio realizado en la clase base se reflejará automáticamente en la clase de “prueba”.

  • Acabo de llegar a la conclusión de que esta es la única forma de hacer la prueba. Sin embargo, se siente un poco extraño hacer esto porque cuando pienso en los especificadores de acceso, pienso en el acceso para el código de producción y no en el código de prueba. Idealmente, me gustaría que hubiera palabras clave “testably_protected” y “testably_private” o algo por el estilo.

    usuario2918461

    20 de septiembre de 2017 a las 16:26


  • Cambiar privado a protegido modifica la encapsulación de la clase. Si hace que su dispositivo de prueba sea un amigo, obtiene el acceso que necesita, pero nadie más lo hace.

    – Aarón Cisne

    29 de agosto de 2018 a las 23:01

Después de muchas horas, esto es lo que decidí que sería la mejor solución para aquellos que quieren probar sus funciones privadas. Esta es una combinación de respuestas de Max DeLiso y Miloš.

Si estás usando boost::unidad-prueba entonces hay una solución fácil y elegante.

  1. Usar protected en lugar de private en tus clases

    /* MyClass.hpp */
    
    class MyClass {
    
    protected:
        int test() {
            return 1;
        }
    };
    
  2. Crear un accesorio:

    /* TestMyClass.cpp */
    
    class F : public MyClass {};
    
    
    BOOST_FIXTURE_TEST_SUITE(SomeTests, F)
    
    // use any protected methods inside your tests
    BOOST_AUTO_TEST_CASE(init_test)
    {
        BOOST_CHECK_EQUAL( test(), 1 );
    }
    BOOST_AUTO_TEST_SUITE_END()
    

De esta manera puede utilizar libremente cualquiera de los MyClass funciones sin #define private public o agregando amigos a tu clase!

  • Acabo de llegar a la conclusión de que esta es la única forma de hacer la prueba. Sin embargo, se siente un poco extraño hacer esto porque cuando pienso en los especificadores de acceso, pienso en el acceso para el código de producción y no en el código de prueba. Idealmente, me gustaría que hubiera palabras clave “testably_protected” y “testably_private” o algo por el estilo.

    usuario2918461

    20 de septiembre de 2017 a las 16:26


  • Cambiar privado a protegido modifica la encapsulación de la clase. Si hace que su dispositivo de prueba sea un amigo, obtiene el acceso que necesita, pero nadie más lo hace.

    – Aarón Cisne

    29 de agosto de 2018 a las 23:01

Avatar de usuario de Max DeLiso
max deliso

El truco de definición es una idea horrible. Reescribir arbitrariamente su código con el preprocesador cuando va a compilarlo nunca es prudente.

Ahora, como varias personas ya han mencionado, es discutible si debería probar métodos privados. Pero esto no cubre el caso en el que intencionalmente ha ocultado constructores para restringir la creación de instancias a ciertos ámbitos, o algunos otros casos más esotéricos.

Además, no puede ser amigo de un espacio de nombres y la “amistad” no se hereda en C ++, por lo que, según el marco de prueba de su unidad, podría tener problemas. Afortunadamente, si está utilizando Boost.Test, hay una solución elegante para este problema en forma de accesorios.

http://www.boost.org/doc/libs/1_52_0/libs/test/doc/html/utf/user-guide/fixture/per-test-case.html

Puede hacer amigo del accesorio y hacer que cree instancias de todas las instancias que usa en las funciones de prueba de su unidad, declarándolas como estáticas para el accesorio y con alcance de módulo. Si está utilizando un espacio de nombres, no se preocupe, puede simplemente declarar su accesorio dentro del espacio de nombres y sus casos de prueba fuera del espacio de nombres, y luego usar el operador de resolución de alcance para llegar a los miembros estáticos.

El BOOST_FIXTURE_TEST_CASE macro se encargará de instanciar y derribar su dispositivo por usted.

  • ¿Qué quieres decir con “amigo del accesorio”? ¿Cómo haces eso? Además, ¿debe estar dentro de un BOOST_FIXTURE_TEST_SUITE? Intentar hacer eso da como resultado que todos los BOOST_AUTO_TEST_CASES dentro no se puedan compilar.

    – Tyler Shellberg

    7 de noviembre de 2021 a las 21:26


  • Nota al margen, el enlace también dice “No puede acceder a miembros privados del dispositivo”

    – Tyler Shellberg

    7 de noviembre de 2021 a las 21:33

¿Ha sido útil esta solución?