Método privado de prueba de unidad en c ++ usando una clase amiga

13

Sé que esta es una práctica debatida, pero supongamos que esta es la mejor opción para mí. Me pregunto cuál es la técnica real para hacer esto. El enfoque que veo es este:

1) Haz una clase de amigos de la clase cuyo método quiero probar.

2) En la clase de amigos, cree uno o varios métodos públicos que llamen a los métodos privados de la clase probada.

3) Prueba los métodos públicos de la clase de amigos.

Aquí hay un ejemplo simple para ilustrar los pasos anteriores:

#include <iostream>

class MyClass
{
  friend class MyFriend; // Step 1

  private:
  int plus_two(int a)
  {
    return a + 2;
  }
};

class MyFriend
{
public:
  MyFriend(MyClass *mc_ptr_1)
  {
    MyClass *mc_ptr = mc_ptr_1;
  }

  int plus_two(int a) // Step 2
  {
    return mc_ptr->plus_two(a);
  }
private:
  MyClass *mc_ptr;
};

int main()
{
  MyClass mc;
  MyFriend mf(&mc);
  if (mf.plus_two(3) == 5) // Step 3
    {
      std::cout << "Passed" << std::endl;
    }
  else
    {
      std::cout << "Failed " << std::endl;
    }

  return 0;
}

Editar:

Veo que en la discusión que sigue a una de las respuestas, la gente se pregunta acerca de mi código base.

Mi clase tiene métodos que son llamados por otros métodos; ninguno de estos métodos debe llamarse fuera de la clase, por lo que deben ser privados. Por supuesto, se podrían poner en un método, pero lógicamente están mucho mejor separados. Estos métodos son lo suficientemente complicados como para justificar las pruebas unitarias y, debido a problemas de rendimiento, es muy probable que tenga que volver a factorizar estos métodos, por lo tanto, sería bueno tener una prueba para asegurarme de que mi nueva creación de factores no rompiera nada. No soy el único que trabaja en el equipo, aunque soy el único que está trabajando en este proyecto, incluidas las pruebas.

Habiendo dicho lo anterior, mi pregunta no se refería a si es una buena práctica escribir pruebas unitarias para métodos privados, aunque aprecio los comentarios.

    
pregunta Akavall 30.09.2014 - 21:50

6 respuestas

20

Una alternativa al amigo (bueno, en cierto sentido) que uso con frecuencia es un patrón que he llegado a conocer como access_by. Es bastante simple:

class A {
  void priv_method(){};
 public:
  template <class T> struct access_by;
  template <class T> friend struct access_by;
}

Ahora, suponga que la clase B está involucrada en la prueba A. Puede escribir esto:

template <> struct access_by<B> {
  call_priv_method(A & a) {a.priv_method();}
}

Luego puedes usar esta especialización de access_by para llamar a los métodos privados de A. Básicamente, lo que hace es poner la responsabilidad de declarar la amistad en el archivo de cabecera de la clase que quiere llamar a los métodos privados de A. También te permite agregar amigos a A sin cambiar la fuente de A. De manera idiomática, también indica a quien lea la fuente de A que A no indica a B un verdadero amigo en el sentido de extender su interfaz. Más bien, la interfaz de A está completa como se indica y B necesita un acceso especial a A (las pruebas son un buen ejemplo, también he usado este patrón al implementar enlaces de python de impulso, a veces una función que necesita ser privada en C ++ es útil para exponer en la capa de python para la implementación).

    
respondido por el Nir Friedman 01.10.2014 - 03:04
9

Si es difícil de probar, está mal escrito

Si tienes una clase con métodos privados lo suficientemente complejos como para justificar su propia prueba, la clase está haciendo demasiado. Dentro hay otra clase que intenta salir.

Extraiga los métodos privados que desea probar en una nueva clase (o clases) y hágalos públicos. Prueba las nuevas clases.

Además de facilitar la prueba del código, esta refactorización facilitará la comprensión y el mantenimiento del código.

    
respondido por el kevin cline 02.10.2014 - 00:22
4

No deberías estar probando métodos privados. Período. Las clases que usan su clase solo se preocupan por los métodos que proporciona, no sobre los que usa debajo del capó para funcionar.

Si está preocupado por la cobertura de su código, necesita encontrar configuraciones que le permitan probar ese método privado desde una de las llamadas de métodos públicos. Si no puedes hacer eso, ¿cuál es el punto de tener el método en primer lugar? Es simplemente un código inalcanzable.

    
respondido por el Ampt 30.09.2014 - 21:53
3

Hay algunas opciones para hacer esto, pero tenga en cuenta que (en esencia) modifican la interfaz pública de sus módulos, para darle acceso a los detalles internos de la implementación (transformando efectivamente las pruebas unitarias en dependencias de clientes estrechamente acopladas, debe tener no dependencias en absoluto).

  • puede agregar una declaración de amigo (clase o función) a la clase probada.

  • podría agregar #define private public al comienzo de sus archivos de prueba, antes de que #include ingrese el código probado. Sin embargo, en caso de que el código probado sea una biblioteca ya compilada, esto podría hacer que los encabezados ya no coincidan con el código binario ya compilado (y causar UB).

  • puede insertar una macro en su clase probada y decidir en una fecha posterior qué significa esa macro (con una definición diferente para el código de prueba). Esto le permitiría realizar pruebas internas, pero también permitiría que un código de cliente de terceros se pirateara en su clase (al crear su propia definición en la declaración que agregue).

respondido por el utnapistim 01.10.2014 - 16:19
2

Aquí hay una sugerencia cuestionable para una pregunta cuestionable. No me gusta el acoplamiento de amigo ya que el código liberado tiene que saber sobre la prueba. La respuesta de Nir es una forma de aliviar eso, pero aún así no me gusta mucho cambiar la clase para ajustarse a la prueba.

Ya que no confío en la herencia a menudo, a veces simplemente hago que los métodos privados sean protegidos y tengo una clase de prueba heredada y expuesta según sea necesario. La realidad es que la API pública y la API de prueba pueden diferir y seguir siendo distintas de la API privada, lo que lo deja en una especie de vínculo.

Este es un ejemplo práctico que es del tipo del que recurro a este truco. Escribo código incrustado, y confiamos bastante en las máquinas de estado. La API externa no necesariamente necesita conocer el estado interno de la máquina de estado, pero la prueba debe (posiblemente) probar la conformidad con el diagrama de la máquina de estado en el documento de diseño. Podría exponer a un captador de "estado actual" como protegido y luego dar el acceso de prueba a eso, permitiéndome probar la máquina de estado más completamente. A menudo, este tipo de clase es difícil de probar como una caja negra.

    
respondido por el J Trana 02.10.2014 - 03:23
0

Puedes escribir tu código con muchas soluciones para evitar que tengas que usar amigos.

Puedes escribir clases y nunca tener ningún método privado. Todo lo que necesita hacer es realizar funciones de implementación dentro de la unidad de compilación, dejar que su clase los llame y pase cualquier dato que los miembros necesitan para acceder.

Y sí, significará que puede cambiar las firmas o agregar nuevos métodos de "implementación" sin cambiar su encabezado en el futuro.

Tienes que sopesar si vale la pena o no. Y mucho dependerá de quién va a ver tu encabezado.

Si estoy usando una biblioteca de terceros, preferiría no ver las declaraciones de amigos a sus evaluadores de unidades. Tampoco quiero construir su biblioteca y hacer que se ejecuten sus pruebas cuando lo haga. Desafortunadamente, muchas bibliotecas de código abierto de terceros que he creado hacen eso.

Las pruebas son el trabajo de los escritores de la biblioteca, no de sus usuarios.

Sin embargo, no todas las clases son visibles para el usuario de su biblioteca. Muchas clases son "de implementación" y usted las implementa de la mejor manera para garantizar que funcionen correctamente. En esos, es posible que aún tengas métodos y miembros privados, pero quieres que los probadores de unidades los prueben. Así que continúe y hágalo de esa manera si eso lleva a un código robusto más rápido, y es fácil de mantener para aquellos que lo necesitan.

Si los usuarios de su clase están todos dentro de su propia empresa o equipo, también puede relajarse un poco más sobre esa estrategia, suponiendo que los estándares de codificación de su empresa lo permiten.

    
respondido por el CashCow 03.10.2014 - 12:28

Lea otras preguntas en las etiquetas