Ventajas y desventajas de hacer un código de andamio de prueba de unidad común

8

Para el proyecto en el que estamos trabajando mi equipo y yo, a menudo encontramos que necesitamos grandes piezas de código de andamios. Crear objetos de dominio con valores correctos, configurar simulacros para repositorios, lidiar con el caché, ... son cosas que ocurren comúnmente durante las pruebas. Muchas veces estamos trabajando con los mismos objetos básicos que son centrales para nuestro dominio (persona, ...) por lo que muchas pruebas crean instancias de estos objetos para que otros objetos trabajen. Tenemos muchas soluciones diferentes que utilizan el dominio base, por lo que este tipo de código a menudo se distribuye entre esas soluciones.

He estado pensando en crear clases comunes que hagan mucho de este andamiaje. Esto nos permitiría solicitar una persona totalmente instanciada con todo configurado (acceso a través del repositorio, almacenamiento en caché ...). Esto elimina el código duplicado de nuestras pruebas unitarias individuales, pero también significaría que hay una gran cantidad de código que probablemente hace "demasiado" por prueba (ya que configuraría un objeto completo y no solo las partes requeridas).

¿Alguien ha hecho esto alguna vez? ¿Hay alguna idea, comentario, pensamiento ... que pueda ofrecer que pueda validar o invalidar este enfoque?

JDT
fuente
Edité su pregunta un poco antes de migrarla aquí para eliminar cosas que normalmente irían a una respuesta. Al enumerar las ventajas y desventajas en la pregunta en sí, reduce la oportunidad de que alguien dé una respuesta integral que no duplique lo que ya dijo. De esta manera, los puntos que destacó surgirán idealmente como parte de las respuestas. Si no lo hacen, no dude en volver a publicarlos como respuesta y la votación de la comunidad puede decirle si tiene razón en el dinero o no.
Adam Lear

Respuestas:

4
Creating domain objects with correct values

Utilizo el patrón de creación para crear objetos de dominio para pruebas como se detalla en el libro " Crecimiento de software orientado a objetos ". Los constructores tienen métodos estáticos que generan valores predeterminados para objetos comunes con métodos para anular los valores predeterminados.

User user = UserBuilder.anAdminUser().withEmail("[email protected]").build();

Puede combinarlos para generar objetos más complejos.

También utilizamos la herencia de clase de caso de prueba con simulacros utilizados comúnmente para tipos específicos de pruebas unitarias; por ejemplo, las pruebas de controlador mvc requieren con frecuencia los mismos simulacros / apéndices para los objetos de solicitud y respuesta.

blanco
fuente
2

La configuración para todas las pruebas puede ser una buena idea, especialmente si realiza una implementación. En lugar de usar simulacros, creamos, implementamos y llenamos una base de datos de prueba y luego apuntamos la prueba a la instancia. No es exactamente la forma en que debe hacerlo según el manual, pero funciona, aún más para nosotros porque utilizamos nuestro código de implementación para hacerlo.

Tony Hopkinson
fuente
Supongamos que no hay dependencia en absoluto de la base de datos y que los objetos son POCO, ¿aún consideraría una base de datos para recuperarlos? ¿Tendría que mantener una base de datos que solo se usa para pruebas diferir de tener que mantener el código para crear objetos que solo se usan para pruebas?
JDT
2

Bedwyr Humphreys está hablando de una solución Builder que he usado un par de veces, y realmente funciona bien. Casi no hay duplicación de código y la construcción de objetos de dominio de una manera agradable y limpia.

User user = UserBuilder.anAdminUser().withEmail("[email protected]").build();

No soy tan fanático de la generación de código (andamios, codemith, lblgen, ...). Solo le ayuda a escribir código duplicado mucho más rápido, no es realmente fácil de mantener. Si un marco de trabajo está causando que escriba un montón de código duplicado (prueba), tal vez necesite encontrar una forma de alejar la base de código del marco.

Si sigue escribiendo código de acceso al repositorio, es posible que pueda resolver esto con un repositorio base genérico y tener una clase de prueba base genérica de la que hereden las pruebas unitarias.

Para evitar la duplicación de código, AOP también puede ayudar para cosas como el registro, el almacenamiento en caché, ...

Pascal Mestdach
fuente
1

Es posible que desee reconsiderar el diseño del código que está probando. ¿Quizás necesita algunas instancias del patrón de diseño de Fachada? Su código de prueba podría usar las fachadas, y sería más simple.


fuente
Es un enfoque válido, pero no para la base de código que tenemos. Actualmente estoy trabajando con Builder-classes que crean mensajes basados ​​en XML a partir de nuestros objetos de dominio. Ninguna cantidad de clases de fachada me impedirá tener que enchufar objetos totalmente construidos en algún momento, algo que he tenido que hacer varias veces en diferentes soluciones.
JDT
1

Me puedo relacionar con esta pregunta. En mi equipo, hemos intentado hacer TDD antes y era la misma historia, demasiadas pruebas requerían muchos métodos de andamiaje / utilidad que simplemente no teníamos. Debido a las presiones de tiempo y algunas otras decisiones no tan correctas, terminamos con pruebas unitarias que fueron muy largas y reinventaron las cosas. En el transcurso de un lanzamiento, esas pruebas unitarias se volvieron tan complicadas y difíciles de mantener que tuvimos que abandonarlas.

Actualmente estoy trabajando con algunos otros desarrolladores para reintroducir TDD en el equipo, pero en lugar de simplemente decirle a la gente, ir a escribir pruebas, queremos hacerlo con cuidado esta vez y las personas tienen las habilidades y las herramientas adecuadas. La ausencia del código de soporte común es una cosa que identificamos donde necesitamos mejorar para que cuando otros comiencen a escribir pruebas, todo el código que escriban sea pequeño y vaya al grano.

Todavía no tengo tanta experiencia en esta área como me gustaría, pero pocas cosas sugeriría en base al trabajo que hemos hecho hasta ahora y algunos deberes que he hecho:

  1. El código común es algo bueno. Hacer el mismo trabajo 5 veces no tiene sentido y, bajo presiones de tiempo, al menos 4 de esas veces estarán a medias.
  2. Mi plan es construir un marco común, luego construir un equipo de liderazgo de TDD compuesto por 4-6 desarrolladores. Luego escriba pruebas unitarias, aprenda el marco y proporcione comentarios sobre él. Luego, pídales que salgan al resto del grupo y guíen a otras personas a producir pruebas unitarias que se puedan mantener y que utilicen el poder del marco. También haga que estos muchachos recopilen comentarios y los traigan nuevamente al marco.
  3. Para los ejemplos que ha proporcionado con la clase Person, en realidad estoy investigando el uso de marcos mockpp o googlemock . Inclinado hacia googlemock por ahora, pero aún no lo he intentado. En cualquier caso, ¿por qué escribir algo que puede ser "prestado"?
  4. Tenga cuidado al crear accesorios "dios" que creen instancias de objetos para todas las pruebas, independientemente de si esas pruebas realmente necesitan esos objetos. Esto es lo que xUnit Test Patterns llama "Patrón de fijación general". Esto podría causar algunos problemas, como a) que cada prueba demore demasiado en ejecutarse debido al código de configuración excesivo yb) las pruebas que terminan teniendo demasiadas dependencias innecesarias. En nuestro marco, cada clase de prueba elige exactamente qué características del marco necesita para ejecutarse, por lo que no se incluye equipaje innecesario (en el futuro podríamos cambiarlo a cada método de selección de forma independiente). Obtuve algo de inspiración de Boost y Modern C ++ Designy se me ocurrió una clase de plantilla base que toma un mpl :: vector de características necesarias y crea una jerarquía de clases personalizada específicamente para esa prueba. Un anillo de decodificador de capitán cruch realmente ingenioso super loco material de plantilla de magia negra :)
DXM
fuente
1

"Cualquier problema en informática puede resolverse con una capa adicional de indirección. Pero eso generalmente creará otro problema". - David Wheeler

(descargo de responsabilidad: soy parte del equipo de JDT, por lo que tengo más conocimiento interno del problema presentado aquí)

El problema esencial aquí es que los objetos en el marco en sí mismos esperan una conexión de base de datos, ya que se basa libremente en el marco CSLA de Rockford Lhotka. Esto hace que burlarse de ellos sea casi imposible (ya que tendrías que cambiar el marco para admitir esto) y también viola el principio de caja negra que debería tener una prueba unitaria.

Su solución propuesta, aunque no es una mala idea en sí misma, no solo requerirá MUCHO trabajo para llegar a una solución estable, sino que también agregará otra capa de clases que debe mantenerse y ampliarse, agregando aún más complejidad a una situación compleja

Si bien estoy de acuerdo en que siempre debe buscar la mejor solución, en una situación del mundo real como esta, ser práctico también tiene sus valores.

Y la practicidad sugiere la siguiente opción:

Utiliza una base de datos.

Sí, sé que técnicamente hablando ya no es una prueba de unidad "real", pero estrictamente hablando en el momento en que cruzas un límite de clase en tu prueba, tampoco es una prueba de unidad real. Lo que debemos preguntarnos aquí es, ¿queremos pruebas unitarias 'puras' que requieran un montón de andamios y código de pegamento, o queremos un código comprobable ?

También podría aprovechar el hecho de que el marco utilizado encapsula todo el acceso a la base de datos por usted y ejecuta sus pruebas en una base de datos de sandbox limpia. Si luego ejecuta cada prueba en su propia transacción y revierte al final de cada conjunto 'lógico' de pruebas, no debería tener ningún problema de interdependencia u orden de prueba.

No intentes eliminar una base de datos de una base de datos.

Sam
fuente
Sam, mira mi comentario sobre la respuesta de Tony Hopkinson. ¿Harías lo mismo si estamos tratando con un proyecto que es solo clases de POCO?
JDT
A menos que esté probando un algoritmo que vive solo sin recursos externos, seguro. La idea sería que su base de datos de 'prueba' solo contenga datos mínimos y que cualquier prueba que no sea de infraestructura necesitará ser insertada por las propias pruebas, reduciendo así el costo de mantenimiento. Pero es cierto, con los objetos POCO 'claros', el caso de una base de datos de prueba no es tan fuerte.
Sam
0

Advertiría por tener demasiado código que hace demasiada 'magia negra' sin que los desarrolladores sepan exactamente qué se está configurando. Además, con el tiempo, se volverá muy dependiente de este código / datos. Es por eso que no soy fanático de basar sus pruebas en bases de datos de prueba. Creo que las bases de datos de prueba son para probar como usuario, no para pruebas automatizadas.

Dicho esto, tu problema es real. Haría algunos métodos comunes para configurar los datos que necesita regularmente. Idealmente, intente parametrizar los métodos, porque ciertas pruebas necesitan datos diferentes.

Pero evitaría crear gráficos de objetos grandes. Es mejor tener pruebas ejecutadas con el mínimo de datos que necesitan. Si hay muchos otros datos, puede influir inesperadamente en los resultados de su prueba (peor de los casos).

Me gusta mantener mis pruebas lo más "silos" posible. Sin embargo, es una pregunta subjetiva. Pero elegiría métodos comunes con parámetros para que pueda configurar sus datos de prueba en solo unas pocas líneas de código.

Peter
fuente
0

Todas las posibilidades resumidas en las respuestas:

Utilice el patrón Builder (Bedwyr Humphreys, DXM y Pascal Mestdach)

Ventajas:

  • Sin duplicación de código en pruebas
  • Puede combinar diferentes constructores para pruebas complejas
  • Interfaz limpia
  • Los gráficos de objetos grandes de los constructores pueden influir en el resultado de la prueba

Desventajas

  • Los constructores deben estar escritos y mantenidos
  • El código del constructor también debe ser probado
  • El código del constructor necesitará una preparación y atención constantes

Use una base de datos de prueba (Tony Hopkinson y Sam)

Ventajas:

  • Sencillez
  • Todos los datos en un lugar central
  • Puede usar el código de acceso a datos ya desarrollado
  • Utilizable para objetos de dominio que no son POCO (por ejemplo, Active Record, ...)

Desventajas

  • Requiere el uso del DAL que podría interferir con las pruebas
  • TDD no "puro"
  • 'Cuadro negro', necesita echar un vistazo a la base de datos para ver qué datos contienen sus objetos

Crear objetos de dominio por prueba (Peter)

Ventajas:

  • Código de prueba aislado
  • Sin 'recuadro negro', lo que ves en el código de prueba es lo que obtienes

Desventajas

  • Duplicación de código para objetos comunes

Usar fachadas para objetos de dominio (Raedwald)

Ventajas:

  • Pruebas simples

Desventajas

  • No utilizable en todos los casos.
  • El código oculto detrás de Facade también necesita prueba
JDT
fuente