unit type test should flatspec failure example java unit-testing scala tdd automated-tests

java - type - scalatest



¿Cómo adoptar TDD y asegurar la adherencia? (9)

Soy un ingeniero senior que trabaja en un equipo de cuatro personas más en una aplicación de gestión de contenido propia que impulsa un gran sitio web de deportes de EE. UU. Nos embarcamos en este proyecto hace unos dos años y elegimos Java como nuestra plataforma, aunque mi pregunta no es específica de Java. Desde que empezamos, ha habido algún cambio en nuestras filas. Cada uno de nosotros tiene una latitud significativa para decidir los detalles de la implementación, aunque las decisiones importantes se toman por consenso.

El nuestro es un proyecto relativamente joven, sin embargo, ya estamos en un punto en el que ningún desarrollador sabe todo sobre la aplicación. Las razones principales de ello son nuestro rápido ritmo de desarrollo, la mayoría de los cuales se produce en una crisis que conduce al inicio de la temporada de nuestro deporte; y el hecho de que nuestra cobertura de prueba es esencialmente 0.

Todos comprendemos los beneficios teóricos de TDD y estamos de acuerdo en principio en que la metodología hubiera mejorado nuestras vidas y la calidad del código si hubiéramos empezado y se hubiera mantenido en ello a través de los años. Esto nunca se concretó, y ahora estamos a cargo de una base de código no probada que todavía requiere mucha expansión y se utiliza activamente en la producción y se basa en la estructura corporativa.

Ante esta situación, solo veo dos soluciones posibles: (1) escribir de forma retroactiva las pruebas para el código existente, o (2) volver a escribir la mayor parte de la aplicación que sea práctica mientras se adhiere fanáticamente a los principios de TDD. Percibo (1) que en general no es práctico porque tenemos un gráfico de dependencia infernal dentro del proyecto. Casi ninguno de nuestros componentes se puede probar de forma aislada; No conocemos todos los casos de uso; y es probable que los casos de uso cambien durante el impulso de prueba debido a los requisitos del negocio o como una reacción a problemas imprevistos. Por estas razones, no podemos estar seguros de que nuestras pruebas resulten de alta calidad una vez que hayamos terminado. Existe el riesgo de llevar al equipo a una falsa sensación de seguridad por la que se introducirán errores sutiles sin que nadie se dé cuenta. Teniendo en cuenta las perspectivas sombrías con respecto al ROI, sería difícil para mí o para nuestro equipo liderar para justificar este esfuerzo de gestión.

El método (2) es más atractivo, ya que seguiremos el principio de la primera prueba, por lo que produciremos un código que está cubierto casi al 100% desde el principio. Incluso si el esfuerzo inicial resulta en islas de código cubierto al principio, esto nos proporcionará una cabeza de playa importante en el camino hacia la cobertura de todo el proyecto y ayudará a desacoplar y aislar los diversos componentes.

La desventaja en ambos casos es que la productividad de nuestro equipo en cuanto a los negocios podría disminuir significativamente o evaporarse por completo durante cualquier esfuerzo de prueba. No podemos darnos el lujo de hacer esto durante la crisis empresarial, aunque a esto le sigue una pausa relativa que podríamos explotar para nuestros propósitos.

Además de elegir el enfoque correcto (ya sea (1), (2) u otro de una solución aún desconocida), necesito ayuda para responder la siguiente pregunta: ¿Cómo puede mi equipo garantizar que nuestro esfuerzo no se desperdicie a largo plazo? ¿Por pruebas no mantenidas y / o por no escribir otras nuevas a medida que los requisitos comerciales continúan? Estoy abierto a una amplia gama de sugerencias aquí, ya sean de zanahorias o palos.

En cualquier caso, gracias por leer acerca de esta situación autoinfligida.


Necesito ayuda para responder la siguiente pregunta: ¿Cómo puede mi equipo garantizar que nuestro esfuerzo no se desperdicie a largo plazo en pruebas no mantenidas y / o en la falla para escribir nuevas a medida que los requisitos comerciales continúan?

Asegúrese de que su proceso de compilación ejecute las pruebas en cada compilación y falle la compilación si hay fallas.

¿Utilizas la integración continua? Hudson es una gran herramienta para esto. Puede mantener un gráfico de # de pruebas, # de fallas, cobertura de pruebas, etc., para cada compilación durante la vida útil de su proyecto. Esto le ayudará a mantener un ojo atento cuando el porcentaje de cobertura esté disminuyendo.

Como mencionó, puede ser bastante difícil adaptar las pruebas de unidad a un proyecto existente, y mucho menos a TDD. ¡Te deseo suerte en este esfuerzo!

Actualización: También quiero señalar que el 100% de cobertura de prueba no es realmente un gran objetivo, tiene rendimientos decrecientes a medida que intenta pasar de ~ 80% o ~ 90%. Para obtener esos últimos pocos puntos porcentuales, debe comenzar a simular cada rama posible en su código. Su equipo comenzará a pasar el tiempo simulando escenarios que no pueden suceder en la vida real ("esta transmisión no dará una excepción IOException cuando la cierre, ¡pero necesito cubrir esta rama!") O no tiene un valor real en su pruebas. Encontré a alguien en mi equipo verificando que if (foo == null) throw new NullPointerException(...); como la primera línea de un método en realidad lanzó una excepción cuando el valor era null .

Mucho mejor para pasar el tiempo probando el código que realmente importa que volverse obsesivo-compulsivo para hacer que cada línea aparezca verde en Emma o Cobertura.


Percibo (1) que en general no es práctico porque tenemos un gráfico de dependencia infernal dentro del proyecto. Casi ninguno de nuestros componentes se puede probar de forma aislada; No conocemos todos los casos de uso;

Este es tu verdadero problema.

Puede comenzar escribiendo pruebas de integración que esencialmente automatizan su proceso de prueba. Usted obtiene el valor de este derecho de la puerta. Lo que necesita es una red de seguridad para refactorizar que le dará una pista cada vez que haya roto el código. Tome la mayor cantidad de transacciones que pueda para ejercer la aplicación, automatice el proceso de bombeo y comparando lo esperado con lo real.

Una vez que tenga eso en su lugar, comience a intentar romper parte de ese gráfico de dependencia para que pueda aislar las piezas para probar.

Siempre que tenga un error que corregir, escriba una prueba de unidad para ello que replique el error. Luego arregla el código y vuelve a ejecutar la prueba. Debería ver la prueba pasar con éxito. Haga de este su procedimiento estándar de seguimiento y corrección de errores. Esa lista de pruebas crecerá como interés en una cuenta bancaria.

Estoy de acuerdo con la recomendación de CI Agregue métricas de cobertura de código a Hudson o al control de crucero.

No hace falta decir que está usando Subversion, Git u otro sistema de administración de código fuente, ¿verdad?

Es un proceso largo, pero al final vale la pena.


Creo que necesitas voltear parte de la pregunta y los argumentos relacionados. ¿Cómo puede asegurarse de que la adhesión a TDD no resulte en un esfuerzo inútil?

No puedes, pero lo mismo es cierto para cualquier proceso de desarrollo.

Siempre me ha parecido un poco paradójico que siempre tengamos el desafío de probar el beneficio en efectivo de TDD y las disciplinas ágiles relacionadas, cuando al mismo tiempo los procesos tradicionales de cascada con mayor frecuencia no dan lugar a

  • plazos perdidos
  • presupuestos soplados
  • marchas de la muerte
  • agotamiento del desarrollador
  • software de buggy
  • clientes insatisfechos

TDD y otras metodologías ágiles intentan abordar estos problemas, pero obviamente introducen algunas preocupaciones nuevas.

En cualquier caso, me gustaría recomendar los siguientes libros que pueden responder algunas de sus preguntas con mayor detalle:


Esto puede sonar extraño, pero ¿qué problemas reales tiene ahora? No dices qué problemas está experimentando el negocio.

Claro, si me uniera a tu equipo, estaría haciendo TDD todo lo que pudiera, he estado comprometido a practicar TDD durante muchos años, pero también he visto bases de código feas y quería limpiarlas, pero no se entendió lo suficiente como para estar seguro de que los cambios en el código fuente que quería hacer para mejorar la capacidad de prueba fueran refactorizaciones reales.

Parece que tienes una buena idea de la realidad de la situación, pero ¿puedes probar que hacer TDD mejoraría la situación? ¿Tienes alguna métrica que resalte tus problemas? Si tiene, entonces puede utilizar mejores prácticas para mejorarlos.

De acuerdo, después de todo eso, mi consejo práctico sería comenzar usando la relative lull para hacer lo que pueda, incluyendo: documentar casos de prueba, escribir pruebas automatizadas, refactorizar para reducir dependencias, escribir código nuevo con TDD, capacitar a sus programadores para Ser eficaz con TDD, etc.


Junto con algunas de las excelentes sugerencias ya, voy a hacer una pausa con dos puntos.

  • Comience a escribir casos de prueba para todos los nuevos códigos. Esto es una necesidad y usted necesita hacerla parte de su cultura.
  • Comience a escribir pruebas para replicar cualquier error que encuentre en el código existente. Antes de corregir cualquier error que encuentre en su base de código existente, escriba un caso de prueba reproducible para ese error. Esto le permitirá al menos comenzar a presentar casos de prueba contra áreas que son problemas conocidos en su código. Aunque lo ideal sería escribir pruebas contra todo el código existente, esto rara vez es posible, por lo menos al menos abordar problemas conocidos.

Además, definitivamente estoy de acuerdo con el Hudson para sugerencias de IC. Además, si aún no lo está haciendo, haga revisiones por pares para todos los códigos ingresados ​​también. Esto no tiene que ser un proceso formal extenso.

Por ejemplo, simplemente tenemos que asignar cualquier tarea completada (a través de JIRA) a un estado de Revisión de Código cuando el desarrollador haya terminado. El desarrollador elegirá a otro desarrollador para asignar esta tarea y tomamos un tiempo de respuesta de 48 horas en las revisiones de código. La tarea es marcada como resuelta por el revisor, no por el desarrollador. Esto le da cierta confianza adicional a la calidad del código, la cobertura de la prueba y el diseño. Las tareas nunca son resueltas por la persona que las implementó. Un beneficio adicional es que expone el código a otros, por lo que hay al menos algo de transferencia de conocimiento inherente al proceso.


Las pruebas de ajuste retroactivo le permitirán mejorar su diseño y encontrar errores. Tratar de reescribir grandes franjas de su aplicación solo resultará en que pierda su fecha límite y termine con un montón de funcionalidades a medio trabajar. Mayor reescribe casi nunca funciona. Ir a la opción (1).

Ah, y solo tener que hudson correr y hacer quejarse cuando rompes las pruebas (a través del correo electrónico) es probablemente suficiente para ponerte en contacto con el espíritu de las cosas.


Me he encontrado con esto en el pasado con algunos equipos. Cuando he sido el líder tecnológico en el proyecto, esta es la forma en que lo abordé. Primero, hice que el desarrollador escribiera cuál sería el caso de prueba (a través de un documento de diseño o similar). De esa manera, no solo tenemos el caso de prueba, sino que me hace recordar que entendieron el cambio para poder verificar que saben lo que están haciendo.

Después de eso, para las pruebas que no son de GUI, hago que el desarrollador use JUnit o algo así para asegurarme de que sea bueno. Como el caso de prueba ya está definido, esto no toma mucho tiempo.

Dependiendo de la complejidad del caso de prueba regresiva, puede llevar un poco más de tiempo, pero después de explicar la mejor capacidad de mantenimiento, hay menos errores debido a que se ha cambiado el código existente con este tipo de pruebas y cosas así ... Por lo general, he podido insistir. mediante.

Espero que esto ayude.


Recomendaría que todos los códigos nuevos y correcciones de errores requieran una prueba de unidad. Período.

Luego, volvería (antes de refactorizar el código anterior) y escribiría las pruebas unitarias para aquellas piezas de código que son las más críticas para el negocio, tienen la mayoría de los errores y son menos comprendidas.


"La desventaja en ambos casos es que la productividad de nuestro equipo en cuanto a los negocios podría disminuir significativamente o evaporarse por completo durante cualquier esfuerzo de prueba".

Esta es una mala interpretación común de los hechos. Ahora mismo tienes un código que no te gusta y te cuesta mantenerlo. "Gráfico de dependencia infernal", etc.

Por lo tanto, el "crujido" del desarrollo que ha estado haciendo ha llevado a un retrabajo costoso. Trabajo tan caro que no te atreves a intentarlo. Eso dice que tu desarrollo crujiente no es muy efectivo. Parece barato en ese momento, pero en retrospectiva, observa que realmente está desperdiciando el dinero del desarrollo porque creó un software costoso y problemático en lugar de crear un buen software.

TDD puede cambiar esto para que no esté produciendo un software crujiente que es costoso de mantener. No puede solucionarlo todo, pero puede dejar claro que cambiar su enfoque de "crisis" puede producir un mejor software que es menos costoso a largo plazo.

Según su descripción, parte (o la totalidad) de su base de código actual es un pasivo, no un activo. Ahora piense qué hará TDD (o cualquier disciplina) para reducir el costo de esa responsabilidad. La cuestión de la "productividad" no se aplica cuando se produce una responsabilidad.

La regla de oro de TDD: si deja de crear un código que es una responsabilidad, la organización tiene un ROI positivo.

Tenga cuidado de preguntar cómo mantener su ritmo actual de productividad. Parte de esa "productividad" está produciendo costos sin valor.

"Casi ninguno de nuestros componentes se puede probar de forma aislada; no conocemos todos los casos de uso"

Correcto. Las pruebas unitarias de ajuste a una base de código existente son realmente difíciles.

"Existe el riesgo de llevar al equipo a una falsa sensación de seguridad por la que se introducirán errores sutiles sin que nadie se dé cuenta"

Falso. No hay "falsa sensación de seguridad". Todos saben que las pruebas son, en el mejor de los casos, difíciles.

Además, ahora tienes bichos horribles. Tiene problemas tan graves que ni siquiera sabe cuáles son, porque no tiene cobertura de prueba.

El intercambio de algunos errores sutiles sigue siendo una gran mejora con respecto al código que no puede probar. Tomaré errores sutiles sobre errores desconocidos cualquier día.

"El método (2) es más atractivo"

Sí. Pero.

Sus esfuerzos de prueba anteriores fueron subvertidos por una cultura que premia la programación de crisis.

¿Ha cambiado algo? Lo dudo. Tu cultura sigue recompensando la programación crujiente. Tu iniciativa de prueba todavía puede ser subvertida.

Deberías mirar un término medio. No se puede esperar que se "adhieran fanáticamente a los principios de TDD" de la noche a la mañana. Eso lleva tiempo, y un cambio cultural significativo.

Lo que necesitas hacer es romper tus aplicaciones en pedazos.

Considere, por ejemplo, el Modelo - Servicios - Ver niveles.

Usted tiene un modelo de aplicación central (cosas persistentes, clases básicas, etc.) que requiere pruebas confiables, rigurosas y exhaustivas.

Tiene servicios de aplicaciones que requieren algunas pruebas, pero están sujetos a "los casos de uso probablemente cambien durante el impulso de las pruebas debido a los requisitos del negocio o como una reacción a problemas imprevistos". Pruebe todo lo que pueda, pero no se oponga al imperativo de enviar las cosas a tiempo para la próxima temporada.

Tiene elementos de visualización / presentación que requieren algunas pruebas, pero no es un procesamiento central. Es solo presentación. Cambiará constantemente a medida que las personas deseen diferentes opciones, vistas, informes, análisis, RIA, GUI, brillo y chisporroteo.