2010-11-13 9 views
47

En C++, a menudo he hecho que una clase de prueba unitaria sea un amigo de la clase que estoy probando. Hago esto porque a veces siento la necesidad de escribir una prueba unitaria para un método privado, o tal vez quiero acceder a algún miembro privado para que pueda configurar más fácilmente el estado del objeto para que pueda probarlo. Para mí, esto ayuda a preservar la encapsulación y la abstracción porque no estoy modificando la interfaz pública o protegida de la clase.¿Qué hay de malo en hacer que un examen de unidad sea un amigo de la clase que está probando?

Si compro una biblioteca de terceros, no me gustaría que su interfaz pública se contamine con un montón de métodos públicos que no necesito saber simplemente porque el proveedor quería una prueba unitaria.

Tampoco quiero tener que preocuparme por un grupo de miembros protegidos que no necesito saber si estoy heredando de una clase.

Es por eso que digo que preserva la abstracción y la encapsulación.

En mi nuevo trabajo, fruncen el ceño en contra de usar clases de amigos incluso para pruebas unitarias. Dicen que la clase no debe "saber" nada sobre las pruebas y que no desea un acoplamiento estricto de la clase y su prueba.

¿Puede alguien explicarme estas razones más para que pueda entender mejor? Simplemente no veo por qué usar un amigo para las pruebas unitarias es malo.

+0

Me pregunto si también han introducido algunas alternativas. –

+0

las alternativas fue encontrar una manera de invocar el método utilizando la interfaz pública; protegerlo y extender la clase; – cchampion

+3

No querer que las pruebas estén estrechamente relacionadas con el código que prueba parece ser que las buenas intenciones salieron mal. El acoplamiento cerrado entre código "normal" generalmente es algo que se debe evitar, pero las pruebas se combinarán con el código que prueban por definición. –

Respuesta

51

Lo ideal sería no tener que probar los métodos privados de prueba en absoluto. Todo lo que un consumidor de tu clase debe preocuparse es la interfaz pública, así que eso es lo que debes probar. Si un método privado tiene un error, debe ser capturado por una prueba unitaria que invoque algún método público en la clase que eventualmente termine llamando al método privado con errores. Si un error se escapa, esto indica que los casos de prueba no reflejan completamente el contrato que desea que implemente su clase. La solución a este problema es, casi con seguridad, probar los métodos públicos con más escrutinio, no hacer que sus casos de prueba investiguen los detalles de implementación de la clase.

De nuevo, este es el caso ideal. En el mundo real, las cosas pueden no siempre ser tan claras, y tener una clase de prueba de unidad para ser un amigo de la clase que prueba podría ser aceptable, o incluso deseable. Aún así, probablemente no sea algo que quieras hacer todo el tiempo. Si parece surgir con suficiente frecuencia, podría ser una señal de que sus clases son demasiado grandes y/o realizan demasiadas tareas. Si es así, subdividirlos aún más refaccionando conjuntos complejos de métodos privados en clases separadas debería ayudar a eliminar la necesidad de pruebas unitarias para conocer los detalles de la implementación.

+10

+1 para la sugerencia de que los métodos privados a menudo se pueden hacer públicos en otra clase. – Philipp

+6

No estoy de acuerdo con esto. Si está implementando un algoritmo complejo y desea dividirlo en pequeños pasos (eso sería completamente inútil fuera de la implementación de este algoritmo) no hay nada de malo en querer mantener los pasos como un detalle de implementación privada que ningún otro objeto en el código debería poder acceder, y aún así querer comprobar que cada subpaso devuelve el resultado correcto dadas las entradas correctas. –

+1

+ 1 para @Jean-MichaëlCelerier. Podría tener una función larga a ** que sea pública o dividirla en partes lógicas. Pero las partes lógicas no tienen que ser públicas. Sin embargo, necesitan ser probados. Ex. Una transformación de una imagen donde está deformada. Sin usar bibliotecas que es un código largo si no está roto en partes – navderm

4

Al igual que bcat sugirió, en la medida de lo posible, necesita encontrar errores utilizando la interfaz pública en sí. Pero si desea hacer cosas como imprimir variables privadas y comparar con el resultado esperado, etc. (Útil para que los desarrolladores resuelvan fácilmente los problemas), entonces puede hacer que la clase UnitTest como friend to class sea probada. Pero es posible que deba agregarlo debajo de una macro como a continuación.

class Myclass 
{ 
#if defined(UNIT_TEST) 
    friend class UnitTest; 
#endif 
}; 

Habilite marcar UNIT_TEST solo cuando se requieren pruebas unitarias. Para otras versiones, necesita deshabilitar esta bandera.

12

Debería considerar que hay diferentes estilos y métodos para probar: Black box testing solo prueba la interfaz pública (tratando la clase como una caja negra). Si tienes una clase base abstracta, incluso puedes usar las mismas pruebas contra todas tus implementaciones.

Si usa White box testing, puede incluso ver los detalles de la implementación. No solo acerca de qué métodos privados tiene una clase, sino qué tipo de sentencias condicionales se incluyen (es decir,si desea aumentar la cobertura de su condición porque sabe que las condiciones fueron difíciles de codificar). En las pruebas de caja blanca, definitivamente tiene un "alto acoplamiento" entre las clases/implementación y las pruebas que son necesarias porque desea probar la implementación y no la interfaz.

Como señaló bcat, a menudo es útil usar composición y más clases más pequeñas en lugar de muchos métodos privados. Esto simplifica las pruebas de caja blanca porque puede especificar más fácilmente los casos de prueba para obtener una buena cobertura de prueba.

1

Normalmente, solo prueba la interfaz pública para que pueda rediseñar y refactorizar la implementación. Agregar casos de prueba para miembros privados define un requisito y una restricción en la implementación de su clase.

4

No veo nada de malo con el uso de una clase de prueba de unidad amigo en muchos casos. Sí, descomponer una clase grande en clases más pequeñas a veces es una mejor manera de hacerlo. Creo que las personas son un poco apresuradas para descartar el uso de la palabra clave friend para algo como esto: puede que no sea el diseño ideal orientado a objetos, pero puedo sacrificar un poco de idealismo para una mejor cobertura de prueba si eso es lo que realmente necesito.

0

Proteja las funciones que desea probar. Ahora, en su archivo de prueba de unidad, cree una clase derivada. Crea funciones de contenedor público que llaman a tus funciones protegidas de clase bajo prueba.

+0

amigo tiene mejor encapsulación que protegida. Al proteger a los miembros, le da acceso a todas las clases heredadas e implícitamente dice que nunca los cambiará, ya que son parte de la interfaz heredada. Al poner a los miembros en privado y ofrecer amistad a UNA clase, solo tienes un contrato implícito con esa clase. Y si esa clase es una clase de prueba de unidad, entonces el contrato no es vinculante. – paercebal

9

siento que bcat dio una muy buena respuesta, pero me gustaría exponer en el caso excepcional de que alude a

En el mundo real, las cosas no siempre son tan claras, y que tiene una clase de prueba de la unidad sea un amigo de la clase que prueba podría ser aceptable, o incluso deseable.

Trabajo en una empresa con una gran base de código heredada, que tiene dos problemas que contribuyen a hacer deseable la prueba unitaria de un amigo.

  • Sufren de funciones y clases obscenamente grandes que requieren refactorización, pero para poder refactorizar es útil realizar pruebas.
  • Gran parte de nuestro código depende del acceso a la base de datos, que por diversas razones no debería incluirse en las pruebas unitarias.

En algunos casos imita es útil para aliviar el último problema, pero muy a menudo esto sólo conduce al diseño uneccessarily compleja (jerarquías de clase que de otro modo sería necesario ninguno), mientras que uno podría muy simple refactorizar el código de la siguiente manera:

class Foo{ 
public: 
    some_db_accessing_method(){ 
     // some line(s) of code with db dependance. 

     // a bunch of code which is the real meat of the function 

     // maybe a little more db access. 
    } 
} 

Ahora tenemos la situación en la que la carne de la función necesita refactorización, por lo que nos gustaría una prueba de unidad. No debe exponerse públicamente. Ahora, hay una maravillosa técnica llamada burla que podría usarse en esta situación, pero el hecho es que en este caso una burla es exagerada. Me requeriría aumentar la complejidad del diseño con una jerarquía innecesaria.

Un enfoque mucho más pragmático sería hacer algo como esto:

class Foo{ 
public: 
    some_db_accessing_method(){ 
     // db code as before 
     unit_testable_meat(data_we_got_from_db); 
     // maybe more db code.  
} 
private: 
    unit_testable_meat(...); 
} 

Este último me da todos los beneficios que necesito de la unidad de pruebas, incluyendo darme esa red de seguridad precioso para detectar errores que se producen cuando Refactorizo ​​el código en la carne.Para probarlo en una unidad, tengo que ser amigo de una clase UnitTest, pero estoy firmemente convencido de que esto es mucho mejor que una jerarquía de código inútil para permitirme usar un Mock.

Creo que esto debería convertirse en una expresión idiomática, y creo que es una solución adecuada y pragmática para aumentar el ROI de las pruebas unitarias.

Cuestiones relacionadas