visual unitarios unitarias unit test studio pruebas funcionales ejemplo desventajas crear unit-testing tdd automated-tests agile

unit-testing - unitarias - test unitarios java



Adopción de prueba unitaria (19)

Hemos intentado introducir pruebas unitarias para nuestro proyecto actual, pero parece que no funciona. El código adicional parece haberse convertido en un dolor de cabeza de mantenimiento, ya que cuando cambia nuestro Marco interno tenemos que revisar y corregir las pruebas de unidades que dependen de él.

Tenemos una clase base abstracta para probar las unidades de nuestros controladores que actúa como una plantilla que llama a las implementaciones de métodos abstractos de las clases secundarias, es decir, las llamadas al Framework Initialize para que nuestras clases de controladores tengan su propio método Initialize.

Solía ​​ser un defensor de las pruebas unitarias, pero no parece estar funcionando en nuestro proyecto actual.

¿Alguien puede ayudar a identificar el problema y cómo podemos hacer que las pruebas unitarias funcionen para nosotros en lugar de contra nosotros?


El código adicional parece haberse convertido en un dolor de cabeza de mantenimiento, ya que cuando cambia nuestro Marco interno tenemos que revisar y corregir las pruebas de unidades que dependen de él.

La alternativa es que cuando su Framework cambie, no pruebe los cambios. O no prueba el Framework en absoluto. ¿Es eso lo que quieres?

Puede tratar de refactorizar su Marco para que esté compuesto de piezas más pequeñas que puedan probarse de forma independiente. Luego, cuando su Framework cambie, usted espera que (a) cambien menos piezas o (b) que los cambios sean principalmente en la forma en que se componen las piezas. De cualquier manera, obtendrá una mejor reutilización del código y las pruebas. Pero el verdadero esfuerzo intelectual está involucrado; no esperes que sea fácil.


Consejos:

Evitar escribir código de procedimiento

Las pruebas pueden ser difíciles de mantener si están escritas contra un código de estilo de procedimiento que depende en gran medida del estado global o se encuentra en el fondo de un método feo. Si está escribiendo código en un idioma OO, use construcciones OO de manera efectiva para reducir esto.

  • Evite el estado global si es posible.
  • Evite la estática, ya que tienden a moverse a través de su base de código y, finalmente, causar que las cosas sean estáticas, que no deberían ser. También inflan su contexto de prueba (ver a continuación).
  • Explota el polimorfismo de manera efectiva para prevenir ifs excesivos y banderas

Encuentre qué cambios, encapsule y separe de lo que permanece igual.

Hay puntos de estrangulamiento en el código que cambian mucho más frecuentemente que otras piezas. Haga esto en su código base y sus pruebas serán más saludables.

  • Una buena encapsulación conduce a buenos diseños flojos.
  • Refactorizar y modularizar.
  • Mantenga las pruebas pequeñas y enfocadas.

Cuanto mayor sea el contexto que rodea una prueba, más difícil será mantenerla.

Haga lo que pueda para reducir las pruebas y el contexto circundante en el que se ejecutan.

  • Utilice la refactorización de métodos compuestos para probar trozos de código más pequeños.
  • ¿Estás usando un marco de prueba más nuevo como TestNG o JUnit4? Le permiten eliminar la duplicación en las pruebas al proporcionarle más ganchos de grano fino en el ciclo de vida de la prueba.
  • Investigue usando dobles de prueba (simulacros, falsificaciones, talones) para reducir el tamaño del contexto de prueba.
  • Investigue el patrón del Creador de datos de prueba .

Elimine la duplicación de las pruebas, pero asegúrese de que mantengan el foco.

Probablemente no podrá eliminar todas las duplicaciones, pero aún así trate de eliminarlas cuando le causen dolor. Asegúrese de no eliminar tanta duplicación que alguien no puede entrar y decir lo que hace la prueba de un vistazo. (Ver el artículo "Pruebas de unidades malvadas" de Paul Wheaton para una explicación alternativa del mismo concepto).

  • Nadie querrá arreglar una prueba si no puede entender lo que está haciendo.
  • Sigue el Arrange, Act, Assert Pattern.
  • Use solo una afirmación por prueba.

Pruebe en el nivel correcto lo que está tratando de verificar.

Piense en la complejidad involucrada en una prueba de selenio de grabación y reproducción y qué podría cambiar debajo de usted versus probar un solo método.

  • Aislar las dependencias entre sí.
  • Use la inyección / inversión de control de dependencia.
  • Use los dobles de prueba para inicializar un objeto para la prueba, y asegúrese de estar probando unidades de código de forma aislada.
  • Asegúrese de escribir pruebas relevantes
    • "Spring the Trap" presenta un error a propósito y asegúrese de que quede atrapado por una prueba.
  • Ver también: las pruebas de integración son una estafa

Sepa cuándo usar las pruebas basadas en el estado frente a las interacciones

Las pruebas de unidades verdaderas necesitan un verdadero aislamiento. Las pruebas unitarias no afectan a una base de datos ni a un socket abierto. Deja de burlarte de estas interacciones. Verifique que hable correctamente con sus colaboradores, no que el resultado correcto de esta llamada al método fue "42".

Demostrar el código de conducción de prueba

Es un tema de debate si un equipo determinado tomará o no un examen para conducir todo el código, o escribir "pruebas primero" para cada línea de código. Pero, ¿deberían escribir al menos algunas pruebas primero? Absolutamente. Hay escenarios en los que la primera prueba es, sin duda, la mejor manera de abordar un problema.

Recursos:


¡Buena pregunta!

Diseñar buenas pruebas unitarias es difícil como diseñar el software en sí mismo. Esto rara vez es reconocido por los desarrolladores, por lo que el resultado a menudo es pruebas de unidades escritas apresuradamente que requieren mantenimiento cada vez que el sistema bajo prueba cambia. Por lo tanto, parte de la solución a su problema podría ser pasar más tiempo para mejorar el diseño de las pruebas de su unidad.

Puedo recomendar un gran libro que merece su facturación como The Design Patterns of Unit-Testing

HTH


¿Estás probando unidades de código lo suficientemente pequeñas? No debería ver demasiados cambios a menos que esté cambiando fundamentalmente todo en su código principal.

Una vez que las cosas estén estables, apreciará que la unidad haga más pruebas, pero incluso ahora sus pruebas destacan el grado en que se proponen los cambios en su marco.

Vale la pena, quédate con él lo mejor que puedas.


¿Las pruebas de su unidad no están orientadas también a la caja negra? Quiero decir ... permítanme dar un ejemplo: supongamos que están probando unidades de algún tipo de contenedor, ¿usan el método get () del contenedor para verificar que se haya almacenado realmente un nuevo objeto, o logran manejarlo? el almacenamiento real para recuperar el artículo directamente donde se almacena? El último hace pruebas frágiles: cuando cambias la implementación, estás rompiendo las pruebas.

Debe probar contra las interfaces, no la implementación interna.

Y cuando cambie el marco, será mejor que intente cambiar primero las pruebas y luego el marco.


¿Por qué debería tener que cambiar las pruebas de su unidad cada vez que realiza cambios en su marco? ¿No debería ser esto al revés?

Si está usando TDD, primero debe decidir que sus pruebas están probando el comportamiento incorrecto, y que en su lugar deben verificar que exista el comportamiento deseado. Ahora que ha arreglado sus pruebas, sus pruebas fallan, y tiene que ir aplastar los errores en su marco hasta que sus pruebas pasen nuevamente.


Bueno, si la lógica ha cambiado en el código y ha escrito pruebas para esos fragmentos de código, supongo que las pruebas deberían modificarse para verificar la nueva lógica. Se supone que las pruebas unitarias son un código bastante simple que prueba la lógica de su código.


Descubrí que, a menos que utilice una metodología de IoC / DI que fomente la escritura de clases muy pequeñas y siga religiosamente el Principio de Responsabilidad Individual, las pruebas unitarias terminan probando la interacción de múltiples clases, lo que las hace muy complejas y, por lo tanto, frágiles.

Mi punto es que muchas de las nuevas técnicas de desarrollo de software solo funcionan cuando se usan juntas. Particularmente MVC, ORM, IoC, pruebas unitarias y burlas. El DDD (en el sentido primitivo moderno) y el TDD / BDD son más independientes, por lo que puedes usarlos o no.


En algún momento, el diseño de las pruebas TDD lanza cuestionamientos sobre el diseño de la aplicación en sí. Compruebe si sus clases han sido bien diseñadas, sus métodos solo realizan una cosa al mismo tiempo ... Con un buen diseño, debería ser simple escribir código para probar el método y las clases simples.


He estado pensando sobre este tema yo mismo. Estoy muy convencido del valor de las pruebas unitarias, pero no del estricto TDD. Me parece que, hasta cierto punto, puedes estar haciendo una programación exploratoria donde la forma en que las cosas se han dividido en clases / interfaces va a tener que cambiar. Si ha invertido mucho tiempo en pruebas unitarias para la estructura de la clase anterior, eso aumenta la inercia contra la refactorización, es doloroso descartar ese código adicional, etc.


Las pruebas de tu unidad están haciendo lo que se supone que deben hacer. Traer a la superficie cualquier interrupción en el comportamiento debido a cambios en el marco, código inmediato u otras fuentes externas. Lo que se supone que debe hacer es ayudarlo a determinar si el comportamiento cambió y las pruebas de la unidad deben modificarse en consecuencia, o si se introdujo un error que hace que la prueba de la unidad falle y deba corregirse.

No te rindas, mientras que es frustrante ahora, el beneficio se realizará.


No estoy seguro de los problemas específicos que dificultan la realización de pruebas para su código, pero puedo compartir algunas de mis propias experiencias cuando tuve problemas similares con la interrupción de mis pruebas. Finalmente aprendí que la falta de capacidad de prueba se debía en gran parte a algunos problemas de diseño con la clase bajo prueba:

  • Usar clases concretas en lugar de interfaces
  • Usando singletons
  • Llamando a muchos métodos estáticos para lógica de negocios y acceso a datos en lugar de métodos de interfaz

Debido a esto, descubrí que, por lo general, mis exámenes se estaban interrumpiendo, no debido a un cambio en la clase bajo prueba, sino debido a cambios en otras clases que la clase bajo prueba llamaba. En general, las clases de refactorización para solicitar sus dependencias de datos y las pruebas con objetos simulados (EasyMock y otros para Java) hacen que las pruebas sean mucho más centradas y mantenibles. Realmente disfruté algunos sitios en particular sobre este tema:


Si el problema es que sus pruebas se están desactualizando con el código real, podría hacer una o ambas de las siguientes:

  1. Capacite a todos los desarrolladores para que no aprueben revisiones de códigos que no actualicen las pruebas unitarias.
  2. Configure un cuadro de prueba automático que ejecute el conjunto completo de pruebas de unidades después de cada check-in y envíe un correo electrónico a los que rompan la compilación. (Solíamos pensar que eso era solo para los "grandes", pero utilizamos un paquete de código abierto en una caja dedicada).

Si su código es realmente difícil de probar y el código de prueba se rompe o requiere mucho esfuerzo para mantenerse sincronizado, entonces usted tiene un problema mayor.

Considere usar la refactorización de método de extracción para sacar pequeños bloques de código que hacen una cosa y una sola cosa; sin dependencias y escribe tus pruebas a esos pequeños métodos.


Sin más información, es difícil dar una idea de por qué estás sufriendo estos problemas. A veces es inevitable que cambiar las interfaces, etc., romperá muchas cosas, otras veces se debe a problemas de diseño.

Es una buena idea tratar de categorizar los fallos que está viendo. ¿Qué tipo de problemas tienes? Por ejemplo, ¿es el mantenimiento de la prueba (como hacerlos compilar después de la refactorización) debido a cambios en la API, o depende del comportamiento del cambio de la API? Si puede ver un patrón, puede intentar cambiar el diseño del código de producción o aislar mejor las pruebas de los cambios.

Si al cambiar un puñado de cosas provoca una devastación incalculable en su suite de pruebas en muchos lugares, hay algunas cosas que puede hacer (la mayoría de ellas son solo consejos comunes para pruebas de unidades):

  • Desarrolla pequeñas unidades de código y prueba pequeñas unidades de código. Extraiga interfaces o clases base donde tenga sentido para que las unidades de código tengan ''costuras'' en ellas. Cuantas más dependencias tenga que aprovechar (o, lo que es peor, instanciar dentro de la clase usando ''nuevo''), más expuesto estará a cambiar su código. Si cada unidad de código tiene un puñado de dependencias (a veces una pareja o ninguna), entonces es mejor aislarse del cambio.

  • Solo afirme lo que necesita la prueba. No afirmar en un estado intermedio, incidental o no relacionado. Diseñe por contrato y realice una prueba por contrato (por ejemplo, si está probando un método de pila emergente, no pruebe la propiedad de recuento después de presionar, lo que debería hacerse en una prueba separada).

    Veo este problema bastante, especialmente si cada prueba es una variante. Si algo de ese estado incidental cambia, rompe todo lo que afirma (independientemente de si las afirmaciones son necesarias o no).

  • Al igual que con el código normal, use fábricas y constructores en sus pruebas unitarias. Aprendí eso cuando alrededor de 40 pruebas necesitaban una llamada de constructor actualizada después de un cambio de API ...

  • Igualmente importante es usar la puerta delantera primero. Sus pruebas siempre deben usar el estado normal si está disponible. Solo usa pruebas basadas en interacciones cuando es necesario (es decir, no hay estado para verificar contra).

De todos modos, la esencia de esto es que trataría de descubrir por qué / dónde se están rompiendo las pruebas y partir de allí. Haz tu mejor esfuerzo para aislarte del cambio.


Sugeriría invertir en una herramienta de automatización de pruebas. Si usa integración continua puede hacerlo funcionar en tándem. Allí hay herramientas que escanearán su base de código y generarán pruebas para usted. Entonces los ejecutará. La desventaja de este enfoque es que es demasiado genérico. Porque en muchos casos el propósito de la prueba unitaria es romper el sistema. He escrito numerosas pruebas y sí tengo que cambiarlas si la base de código cambia.

Hay una línea fina con la herramienta de automatización que definitivamente tendría una mejor cobertura de código.

Sin embargo, con pruebas bien desarrolladas también probará la integridad del sistema.

Espero que esto ayude.


Todo viene con precio, por supuesto. En esta etapa temprana de desarrollo, es normal que se tengan que cambiar muchas pruebas unitarias.

Es posible que desee revisar algunos bits de su código para hacer más encapsulación, crear menos dependencias, etc.

Cuando estés cerca de la fecha de producción, estarás feliz de que tengas esas pruebas, créeme :)


Uno de los beneficios de las pruebas unitarias es que cuando realiza cambios como este puede probar que no está rompiendo su código. Tienes que mantener tus pruebas sincronizadas con tu marco de trabajo, pero este trabajo más bien mundano es mucho más fácil que tratar de descubrir qué se rompió cuando refactorizaste.


Insistiría en que te quedes con el TDD. Intente verificar su marco de Pruebas unitarias haga un RCA (Análisis de causa raíz) con su equipo e identifique el área.

Repare el código de prueba de la unidad a nivel de suite y no cambie su código con frecuencia, especialmente los nombres de funciones u otros módulos.

Le agradecería que si puede compartir su estudio de caso bien, entonces podemos obtener más información en el área problemática?