¿Prefieres hacer cosas privadas internas / públicas para las pruebas, o usar algún tipo de pirateo como PrivateObject?

26

Soy un principiante en las pruebas de código, y fui una assertprostituta antes. Una cosa que me preocupa en las pruebas unitarias es que a menudo requiere que usted haga public(o al menos internal) campos que de privateotro modo habrían sido , para eliminarlos readonly, crear privatemétodos protected virtual, etc.

Recientemente descubrí que puedes evitar esto usando cosas como la clase PrivateObject para acceder a cualquier cosa en un objeto a través de la reflexión. Pero esto hace que sus pruebas sean menos mantenibles (las cosas fallarán en la ejecución en lugar del tiempo de compilación, se romperá con un simple cambio de nombre, es más difícil de depurar ...). Qué opinas de esto ? ¿Cuáles son las mejores prácticas en pruebas unitarias con respecto a la restricción de acceso?

editar: considere, por ejemplo, que tiene una clase con un caché en un archivo en el disco, y en sus pruebas desea escribir en la memoria.

Zonko
fuente
2
La gente de Python solo tiene público. Están contentos. ¿Por que preocuparse? ¿Por qué no hacer que todo sea público?
S.Lott
12
Porque eso está lejos de las mejores prácticas en la mayoría de los principales idiomas. La verdadera respuesta es usar buenas interfaces y cosas así para que pueda usar objetos simulados para realizar pruebas en contra.
Plataforma
2
@Rig: ¿Python no es un idioma superior? Interesante.
S.Lott
77
"está lejos de las mejores prácticas en la mayoría de los principales idiomas" no implica lógicamente (ni alude a eso) "Python no es un idioma superior".
Mike Nakis
1
Precisamente, es un idioma superior, pero la mayoría de los idiomas principales no son Python y respaldan la configuración del alcance adecuado. Mantengo mi declaración. Hay patrones diseñados para hacer un software altamente comprobable mientras se asegura que las variables mantengan el alcance. Incluso los programadores de Python tienden a emular el alcance con prefijos de lo que he visto.
Plataforma

Respuestas:

36

Nunca deberías tener que

hacer public(o por lo menos internal) campos que habrían sido privatede otro modo, a UN- readonly, hacerlos privatemétodos protected virtualvez

Especialmente la no lectura de un campo puede hacer que un objeto inmutable sea mutable, y eso sería un desastre.

Sus pruebas deben considerar sus objetos como cajas negras ( Wikipedia ), lo que significa que solo deben preocuparse por la interfaz pública de los objetos, no con los detalles de su implementación.

Si un objeto no puede probarse suficientemente usando su interfaz pública, entonces necesita encontrar formas de proporcionar extensiones formales y útiles a su interfaz que faciliten la prueba. Por ejemplo, un sistema de registro con solo un par de métodos de Registro / Desregistro quizás se beneficiaría de un método IsRegistered incluso si no es necesario para la aplicación en cuestión; aun así, es una extensión formal y útil de la interfaz, y, por cierto, acomodará las pruebas.

Lo importante es que cambiar la implementación del objeto no debería requerir que cambie la prueba de la unidad. En teoría, debería poder escribir la prueba unitaria una vez, y luego pedirle a múltiples programadores que codifiquen múltiples implementaciones completamente diferentes del objeto a probar para trabajar con la prueba unitaria.

Mike Nakis
fuente
2
¡Exactamente! Si no puede probarlo sin reflejos o hacks, entonces probablemente haya cometido un error en su API o su diseño.
Falcon
¿Está diciendo que hacer privatemétodos protected virtualpara ayudar con la burla en las pruebas se considera una mala práctica?
Robula
1
@Robula En mi libro, sí. Ni siquiera escribo mis pruebas en el mismo paquete que el código de producción, porque no tengo uso para poder acceder a miembros no públicos. Yo tampoco uso simulacros. Por supuesto, si tiene que usar simulacros, entonces tendrá que hacer lo que sea necesario para facilitar el simulacro, por lo que la protección virtual puede ser un compromiso práctico en muchas situaciones. Pero idealmente, un diseño no necesita simulacros, solo implementaciones alternativas, e idealmente las pruebas son pruebas de caja negra, no pruebas de caja blanca, por lo que las cosas se prueban en integración, sin simulaciones.
Mike Nakis
13

En C # puede usar InternalsVisibleToAttributepara permitir que su ensamblaje de prueba vea las internalclases en el ensamblaje que está probando. Parece que ya lo sabes.

En la mayoría de los casos, solo estoy interesado en probar la API pública de mi ensamblado. En un caso en el que quiero hacer una prueba de caja blanca de alguna unidad, muevo el código a una internalclase y lo convierto en un publicmétodo de esa clase. Entonces puedo codificar una prueba unitaria para esa clase.

Esto se presta bien a la inyección de dependencia y al patrón de estrategia. Puede abstraer el método en una interfaz, inyectar la interfaz en el constructor de su objeto padre y simplemente probar que el padre delegue adecuadamente. Luego puede probar que el objeto secundario se comporta adecuadamente. Esto hace que todo sea más modular.

Scott Whitlock
fuente
8

En mi experiencia, es mejor no exponer las partes internas de su clase especialmente para el examen. Aunque parezca que esto hace que la prueba sea más fácil de escribir. La prueba es el primer cliente de su clase, por lo que si es difícil probar su clase a través de su interfaz pública, probablemente será difícil usar su clase en otros lugares también.

Lo fácil que es probar la clase a través de su API pública son comentarios sobre lo fácil que será usar la clase. Piense en las pruebas parcialmente como una forma de crear un prototipo del uso de su clase.

Intente considerar por qué su prueba necesita detectar algo sobre la clase que no está disponible a través de la API pública. ¿Quizás su clase está haciendo demasiado y necesita descomponerse en un grupo de clases cooperativas? Luego puede burlarse de algunas de las clases colaboradoras para sentir lo que está haciendo la clase bajo prueba.

Intente aplicar el principio de inversión de dependencia e introduzca interfaces entre sus clases que pueda burlarse en sus pruebas. Puede proporcionar a los colaboradores a su prueba utilizando la inversión de control .

También considere aplicar el principal "tell don't ask" para ayudar a estructurar la interfaz de su clase de una manera robusta y poco acoplada, donde se exponga la información correcta y se oculte el resto.

flamingpenguin
fuente
6

Personalmente, prefiero dejar el código que estoy probando lo más puro posible y si el código de prueba necesita hacks (y en punteros desagradables en C ++, etc.), estoy feliz de hacerlo.

Hormiga
fuente
1
Estoy de acuerdo, esta es la forma de hacerlo. Mantenga la fealdad en el código de prueba, donde no puede dañar nada.
Alan Delimon
@AlanDelimon ¿No podría dañar la mente de los programadores de mantenimiento posteriores?
flamingpenguin
1
El código feo de @flamingpenguin puede dañar a los programadores en cualquier lugar; pero el código de prueba feo probablemente hará menos daño ya que solo afectará a las personas que modifican la clase y no también a las personas que simplemente lo consumen.
Dan Neely
2
@flamingpenguin Podría, pero si tiene que poner código feo en alguna parte, también podría ser en una prueba unitaria donde es menos probable que necesite modificación y sea parte de la aplicación.
Alan Delimon
5

¡La visibilidad de su código no tiene nada que ver con las pruebas de su unidad!

Usted hace que sus métodos sean privados, no con el propósito de ocultarlos de las pruebas y no los hace públicos para exponerlos a las pruebas, ¿verdad? Esta es su implementación que es esencial aquí, no las pruebas, esos servidores como prueba de su código, pero no son su código .

Hay muchas maneras de habilitar el acceso a métodos y propiedades ocultos (privados o internos). Muchos marcos de prueba e IDEs (por ejemplo, Visual Studio) lo apoyan aquí al generar todo lo necesario para el acceso, solo tiene que crear sus casos de prueba. Entonces, ¿por qué preocuparse? Mejor mantenga su código limpio y ordenado.

Alexander Galkin
fuente