¿Se pueden agregar con éxito las pruebas unitarias a un proyecto de producción existente? Si es así, ¿cómo y si vale la pena?

140

Estoy considerando seriamente agregar pruebas unitarias a un proyecto existente que está en producción. Se inició hace 18 meses antes de que realmente pudiera ver algún beneficio de TDD (palma de la mano) , por lo que ahora es una solución bastante grande con una serie de proyectos y no tengo la más remota idea de por dónde comenzar al agregar pruebas unitarias. Lo que me hace considerar esto es que ocasionalmente un viejo error parece resurgir, o un error se registra como solucionado sin realmente ser reparado. Las pruebas unitarias reducirían o evitarían que ocurrieran estos problemas.

Al leer similar preguntas sobre SO, he visto recomendaciones como comenzar en el rastreador de errores y escribir un caso de prueba para cada error para evitar la regresión. Sin embargo, me preocupa que termine perdiendo el panorama general y las pruebas fundamentales que se habrían incluido si hubiera usado TDD desde el principio.

¿Hay algún proceso / paso que deba cumplirse para garantizar que las soluciones existentes se prueben adecuadamente en la unidad y no solo se incorporen? ¿Cómo puedo asegurarme de que las pruebas son de buena calidad y no son solo el caso de que cualquier prueba sea mejor que ninguna prueba ?

Entonces supongo que lo que también pregunto es;

  • ¿Vale la pena el esfuerzo para una solución existente que está en producción?
  • ¿Sería mejor ignorar las pruebas para este proyecto y agregarlo en una posible reescritura futura?
  • Lo que será más beneficioso; pasar algunas semanas agregando pruebas o algunas semanas agregando funcionalidad?

(Obviamente, la respuesta al tercer punto depende completamente de si estás hablando con la gerencia o un desarrollador)


Motivo de la recompensa

Agregar una recompensa para tratar de atraer una gama más amplia de respuestas que no solo confirme mi sospecha actual de que es algo bueno, sino también algunas buenas razones en contra.

Mi objetivo es escribir esta pregunta más adelante con pros y contras para tratar de mostrarle a la gerencia que vale la pena pasar las horas de trabajo para trasladar el desarrollo futuro del producto a TDD. Quiero abordar este desafío y desarrollar mi razonamiento sin mi propio punto de vista parcial.

djdd87
fuente
11
Enlace obligatorio al libro de Michael Feathers sobre el tema: amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/…
Mark Rushakoff
1
@ Mark - Gracias, está en el enlace que proporcioné. Esperaba obtener una respuesta decente sin comprar otro libro ... aunque nunca se pueden tener demasiados libros (a menos que se quiera hacer el trabajo).
djdd87
1
Realmente necesitas leer este libro :) Es uno de mis favoritos y realmente ayuda a comprender la tensión entre la refactorización y las pruebas automatizadas.
manuel aldana

Respuestas:

177

He introducido pruebas unitarias para codificar bases que no lo tenían anteriormente. El último gran proyecto en el que estuve involucrado fue cuando el producto ya estaba en producción con pruebas de unidad cero cuando llegué al equipo. Cuando me fui, 2 años después, tuvimos más de 4500 pruebas que arrojaron aproximadamente un 33% de cobertura de código en una base de código con más de 230 000 LOC de producción (aplicación Win-Forms financiera en tiempo real). Eso puede sonar bajo, pero el resultado fue una mejora significativa en la calidad del código y la tasa de defectos, además de una mejor moral y rentabilidad.

Se puede hacer cuando se tiene un entendimiento preciso y un compromiso de las partes involucradas.

En primer lugar, es importante comprender que las pruebas unitarias son una habilidad en sí mismas. Puede ser un programador muy productivo según los estándares "convencionales" y aún así tener dificultades para escribir pruebas unitarias de una manera que se amplíe en un proyecto más grande.

Además, y específicamente para su situación, agregar pruebas unitarias a una base de código existente que no tiene pruebas también es una habilidad especializada en sí misma. A menos que usted o alguien en su equipo tenga experiencia exitosa con la introducción de pruebas unitarias a una base de código existente, diría que lee el libro de Feather es un requisito (no opcional ni muy recomendable).

Hacer la transición a la prueba unitaria de su código es una inversión en personas y habilidades tanto como en la calidad de la base del código. Comprender esto es muy importante en términos de mentalidad y gestión de expectativas.

Ahora, para sus comentarios y preguntas:

Sin embargo, me preocupa que termine perdiendo el panorama general y las pruebas fundamentales que se habrían incluido si hubiera usado TDD desde el principio.

Respuesta corta: Sí, perderá las pruebas y sí, es posible que inicialmente no se vean como lo tendrían en una situación de campo verde.

La respuesta a nivel más profundo es esta: no importa. Comienzas sin pruebas. Comience a agregar pruebas y refactorice a medida que avanza. A medida que los niveles de habilidad mejoren, comience a subir el listón para todo el código recién escrito agregado a su proyecto. Sigue mejorando, etc.

Ahora, leyendo entre líneas aquí, tengo la impresión de que esto proviene de la mentalidad de "la perfección como una excusa para no tomar medidas". Una mejor mentalidad es centrarse en la autoconfianza. Entonces, como es posible que aún no sepa cómo hacerlo, descubrirá cómo hacerlo y completará los espacios en blanco. Por lo tanto, no hay razón para preocuparse.

De nuevo, es una habilidad. No puede pasar de cero pruebas a TDD-perfección en un enfoque de "proceso" o "paso a paso" de libro de cocina de forma lineal. Será un proceso Sus expectativas deben ser hacer progresos y mejoras graduales e incrementales. No hay una pastilla magica.

La buena noticia es que a medida que pasan los meses (e incluso años), su código gradualmente comenzará a convertirse en un código "adecuado", bien factorizado y probado.

Como nota al margen. Encontrará que el obstáculo principal para introducir pruebas unitarias en una base de código antigua es la falta de cohesión y las dependencias excesivas. Por lo tanto, probablemente encontrará que la habilidad más importante será cómo romper las dependencias existentes y desacoplar el código, en lugar de escribir las pruebas unitarias reales.

¿Hay algún proceso / paso que deba cumplirse para garantizar que las soluciones existentes se prueben adecuadamente en la unidad y no solo se integren?

A menos que ya lo tenga, configure un servidor de compilación y configure una compilación de integración continua que se ejecute en cada registro, incluidas todas las pruebas unitarias con cobertura de código.

Entrena a tu gente.

Comience en algún lugar y comience a agregar pruebas mientras progresa desde la perspectiva del cliente (ver más abajo).

Use la cobertura de código como referencia guía de la cantidad de código base de producción que se está probando.

El tiempo de construcción siempre debe ser RÁPIDO. Si su tiempo de construcción es lento, sus habilidades de prueba de unidad están rezagadas. Encuentra las pruebas lentas y mejóralas (desacopla el código de producción y prueba de forma aislada). Bien escrito, debería poder realizar varios miles de pruebas unitarias y aún así completar una compilación en menos de 10 minutos (~ 1 ms / prueba es una guía buena pero muy aproximada, pueden aplicarse algunas excepciones como código usando la reflexión, etc. )

Inspeccionar y adaptar.

¿Cómo puedo asegurarme de que las pruebas son de buena calidad y no son solo el caso de que cualquier prueba sea mejor que ninguna prueba?

Su propio juicio debe ser su fuente principal de realidad. No hay métrica que pueda reemplazar la habilidad.

Si no tiene esa experiencia o juicio, considere contratar a alguien que sí.

Dos indicadores secundarios aproximados son la cobertura total del código y la velocidad de construcción.

¿Vale la pena el esfuerzo para una solución existente que está en producción?

Si. La gran mayoría del dinero gastado en un sistema o solución personalizada se gasta después de su puesta en producción. E invertir en calidad, las personas y las habilidades nunca deberían estar fuera de moda.

¿Sería mejor ignorar las pruebas para este proyecto y agregarlo en una posible reescritura futura?

Debería tener en cuenta, no solo la inversión en personas y habilidades, sino lo más importante, el costo total de propiedad y el tiempo de vida esperado del sistema.

Mi respuesta personal sería "sí, por supuesto" en la mayoría de los casos porque sé que es mucho mejor, pero reconozco que puede haber excepciones.

Lo que será más beneficioso; pasar algunas semanas agregando pruebas o algunas semanas agregando funcionalidad?

Ninguno. Su enfoque debe ser agregar pruebas a su base de código MIENTRAS está progresando en términos de funcionalidad.

Una vez más, es una inversión en personas, habilidades y la calidad de la base del código y, como tal, requerirá tiempo. Los miembros del equipo deben aprender cómo romper dependencias, escribir pruebas unitarias, aprender nuevos hábitos, mejorar la disciplina y la conciencia de calidad, cómo diseñar mejor el software, etc. Es importante entender que cuando empiezas a agregar pruebas, los miembros de tu equipo probablemente no tiene estas habilidades aún en el nivel que necesitan para que ese enfoque tenga éxito, por lo que detener el progreso para pasar todo el tiempo para agregar muchas pruebas simplemente no funcionará.

Además, agregar pruebas unitarias a una base de código existente de cualquier tamaño de proyecto considerable es una tarea GRANDE que requiere compromiso y persistencia. No puede cambiar algo fundamental, espere mucho aprendizaje en el camino y pídale a su patrocinador que no espere ningún retorno de la inversión al detener el flujo del valor comercial. Eso no volará, y francamente no debería.

En tercer lugar, desea inculcar valores sólidos de enfoque comercial en su equipo. La calidad nunca llega a expensas del cliente y no puedes ir rápido sin la calidad. Además, el cliente está viviendo en un mundo cambiante, y su trabajo es facilitarle la adaptación. La alineación del cliente requiere calidad y el flujo del valor comercial.

Lo que está haciendo es pagar la deuda técnica. Y lo está haciendo al mismo tiempo que atiende a sus clientes en constante cambio de necesidades. Gradualmente, a medida que se paga la deuda, la situación mejora y es más fácil servir mejor al cliente y ofrecer más valor. Etc. Este impulso positivo es lo que debe aspirar porque subraya los principios del ritmo sostenible y mantendrá y mejorará la moral, tanto para su equipo de desarrollo, su cliente y sus partes interesadas.

Espero que ayude

Mahol25
fuente
Refleja la misma mentalidad que yo. Actualmente estoy en proceso de mostrar este proceso / método exacto para introducir casos de prueba en una aplicación JAVA grande. :)
Darshan Joshi
3
Esta es la mejor respuesta articulada sobre cualquier tema que haya leído en Stackoverflow. ¡Bien hecho! Si no lo ha hecho, le insto a que considere escribir un libro sobre su respuesta a la última pregunta.
Yermo Lamers
Gracias Yermo No estoy seguro de tener tiempo para escribir un libro. Pero tal vez podría escribir un artículo de blog o dos. Acabo de comenzar mi nuevo blog, por lo que puede pasar un tiempo.
Mahol25
2
Este consejo de prueba de la unidad dudes es un consejo de vida general. Seriamente.
Wjdavis5
24
  • ¿Vale la pena el esfuerzo para una solución existente que está en producción?

¡Si!

  • ¿Sería mejor ignorar las pruebas para este proyecto y agregarlo en una posible reescritura futura?

¡No!

  • Lo que será más beneficioso; pasar algunas semanas agregando pruebas o algunas semanas agregando funcionalidad?

Agregar pruebas (especialmente pruebas automatizadas) lo hace mucho más fácil mantener el proyecto funcionando en el futuro, y hace que sea mucho menos probable que envíe problemas estúpidos al usuario.

Pruebas para poner a priori son aquellas que verifican si lo que usted cree que la interfaz pública de su código (y cada módulo en él) está funcionando de la manera que usted piensa. Si puede, intente inducir también cada modo de falla aislado que deberían tener sus módulos de código (tenga en cuenta que esto puede no ser trivial, y debe tener cuidado de no verificar con demasiada atención cómo fallan las cosas, por ejemplo, realmente no desea para hacer cosas como contar la cantidad de mensajes de registro producidos en caso de falla, ya que verificar que esté registrado es suficiente).

Luego, realice una prueba para cada error actual en su base de datos de errores que induzca exactamente el error y que pasará cuando se solucione el error. ¡Entonces arregle esos errores! :-)

Agregar pruebas por adelantado cuesta tiempo, pero le pagan muchas veces en la parte de atrás ya que su código termina siendo de mucha mayor calidad. Eso es muy importante cuando intentas enviar una nueva versión o realizar tareas de mantenimiento.

Compañeros de Donal
fuente
Gracias por la elaborada respuesta. Confirmado lo que estaba sintiendo. Veré qué otras respuestas se publican antes de emitir mis votos y aceptar.
djdd87
15

El problema con la actualización de las pruebas unitarias es que te darás cuenta de que no pensaste en inyectar una dependencia aquí o usar una interfaz allí, y en poco tiempo volverás a escribir todo el componente. Si tiene tiempo para hacer esto, se construirá una buena red de seguridad, pero podría haber introducido errores sutiles en el camino.

He estado involucrado en muchos proyectos que realmente necesitaban pruebas unitarias desde el primer día, y no hay una manera fácil de llevarlos a cabo, salvo una reescritura completa, que generalmente no puede justificarse cuando el código funciona y ya está ganando dinero. Recientemente, he recurrido a escribir scripts de PowerShell que ejercitan el código de una manera que reproduce un defecto tan pronto como se genera y luego mantener estos scripts como un conjunto de pruebas de regresión para cambios posteriores. De esa manera, al menos puede comenzar a construir algunas pruebas para la aplicación sin cambiarla demasiado, sin embargo, estas son más como pruebas de regresión de extremo a extremo que las pruebas unitarias adecuadas.

Fletcher
fuente
Si el código se divide razonablemente bien desde el principio, es bastante fácil probarlo. El problema surge cuando tienes un código que está mal ensamblado y donde los únicos puntos de prueba expuestos son para pruebas de integración completa. (Tengo un código como ese donde la capacidad de prueba es casi nula debido a la cantidad que depende de otros componentes que no son fáciles de burlar, y donde la implementación requiere un reinicio del servidor. Probable ... pero difícil de hacer bien).
Donal Fellows
13

Estoy de acuerdo con lo que la mayoría de los demás ha dicho. Agregar pruebas al código existente es valioso. Nunca estaré en desacuerdo con ese punto, pero me gustaría agregar una advertencia.

Aunque agregar pruebas al código existente es valioso, tiene un costo. Viene a costa de no construir nuevas características. La forma en que estas dos cosas se equilibren depende completamente del proyecto, y hay una serie de variables.

  • ¿Cuánto tiempo te llevará poner a prueba todo ese código? ¿Dias? ¿Semanas? ¿Meses? ¿Años?
  • ¿Para quién escribes este código? ¿Pagando a los clientes? ¿Un profesor? ¿Un proyecto de código abierto?
  • ¿Cómo es tu horario? ¿Tienes plazos difíciles que debes cumplir? ¿Tienes alguna fecha límite?

Nuevamente, permítanme enfatizar, las pruebas son valiosas y usted debe trabajar para poner a prueba su antiguo código. Esto es realmente más una cuestión de cómo lo enfocas. Si puede permitirse soltar todo y poner a prueba todo su código anterior, hágalo. Si eso no es realista, esto es lo que debe hacer al menos

  • Cualquier código nuevo que escriba debe estar completamente bajo prueba unitaria
  • Cualquier código antiguo que toque (corrección de errores, extensión, etc.) debe someterse a prueba unitaria

Además, esta no es una propuesta de todo o nada. Si tiene un equipo de, digamos, cuatro personas, y puede cumplir con sus plazos al poner a una o dos personas en servicio de pruebas heredadas, haga todos los medios.

Editar:

Mi objetivo es escribir esta pregunta más adelante con pros y contras para tratar de mostrarle a la gerencia que vale la pena pasar las horas de trabajo para trasladar el desarrollo futuro del producto a TDD.

Esto es como preguntar "¿Cuáles son las ventajas y desventajas de usar el control de fuente?" o "¿Cuáles son las ventajas y desventajas de entrevistar a las personas antes de contratarlas?" o "¿Cuáles son los pros y los contras de la respiración?"

A veces solo hay un lado del argumento. Necesita tener pruebas automatizadas de alguna forma para cualquier proyecto de cualquier complejidad. No, los exámenes no se escriben solos, y sí, tomará un poco más de tiempo sacar las cosas por la puerta. Pero a la larga tomará más tiempo y costará más dinero reparar los errores después del hecho que escribir pruebas por adelantado. Período. Eso es todo al respecto.

haydenmuhl
fuente
9

Cuando comenzamos a agregar pruebas, se trataba de una base de código de aproximadamente diez millones de años, con demasiada lógica en la interfaz de usuario y en el código de informes.

Una de las primeras cosas que hicimos (después de configurar un servidor de compilación continua) fue agregar pruebas de regresión. Estas fueron pruebas de extremo a extremo.

  • Cada conjunto de pruebas comienza inicializando la base de datos a un estado conocido. De hecho, tenemos docenas de conjuntos de datos de regresión que mantenemos en Subversion (en un repositorio separado de nuestro código, debido al tamaño). El FixtureSetUp de cada prueba copia uno de estos conjuntos de datos de regresión en una base de datos temporal y luego se ejecuta desde allí.
  • La configuración del dispositivo de prueba ejecuta un proceso cuyos resultados nos interesan. (Este paso es opcional; algunas pruebas de regresión existen solo para probar los informes).
  • Luego, cada prueba ejecuta un informe, genera el informe en un archivo .csv y compara el contenido de ese .csv con una instantánea guardada. Estas instantáneas .csvs se almacenan en Subversion junto a cada conjunto de datos de regresión. Si el resultado del informe no coincide con la instantánea guardada, la prueba falla.

El propósito de las pruebas de regresión es decirle si algo cambia. Eso significa que fallan si rompe algo, pero también fallan si cambia algo a propósito (en cuyo caso, la solución es actualizar el archivo de instantánea). No sabe que los archivos de instantáneas son incluso correctos: puede haber errores en el sistema (y luego, cuando corrige esos errores, las pruebas de regresión fallarán).

Sin embargo, las pruebas de regresión fueron una gran victoria para nosotros. Casi todo en nuestro sistema tiene un informe, por lo que al pasar unas semanas obteniendo un arnés de prueba en torno a los informes, pudimos obtener cierto nivel de cobertura en una gran parte de nuestra base de código. Escribir las pruebas unitarias equivalentes habría llevado meses o años. (Las pruebas unitarias nos habrían dado una cobertura mucho mejor y habrían sido mucho menos frágiles; pero preferiría tener algo ahora, en lugar de esperar años para la perfección).

Luego volvimos y comenzamos a agregar pruebas unitarias cuando corregimos errores, o agregamos mejoras, o necesitábamos entender algo de código. Las pruebas de regresión de ninguna manera eliminan la necesidad de pruebas unitarias; son solo una red de seguridad de primer nivel, para que pueda obtener un cierto nivel de cobertura de prueba rápidamente. Luego puede comenzar a refactorizar para romper dependencias, de modo que pueda agregar pruebas unitarias; y las pruebas de regresión le dan un nivel de confianza de que su refactorización no está rompiendo nada.

Las pruebas de regresión tienen problemas: son lentas y hay demasiadas razones por las que pueden romperse. Sin embargo, al menos para nosotros, que eran tan vale la pena. Han detectado innumerables errores en los últimos cinco años, y los detectan en unas pocas horas, en lugar de esperar un ciclo de control de calidad. Todavía tenemos esas pruebas de regresión originales, distribuidas en siete máquinas de construcción continua diferentes (separadas de la que ejecuta las pruebas de unidades rápidas), e incluso las agregamos de vez en cuando, porque todavía tenemos tanto código que nuestros 6,000 + las pruebas unitarias no cubren.

Joe White
fuente
8

Vale la pena. Nuestra aplicación tiene reglas complejas de validación cruzada, y recientemente tuvimos que hacer cambios significativos en las reglas comerciales. Terminamos con conflictos que impedían que el usuario guardara. Me di cuenta de que tomaría una eternidad resolverlo en la aplicación (lleva varios minutos llegar al punto donde estaban los problemas). Quería presentar pruebas unitarias automatizadas e instalar el marco, pero no había hecho nada más que un par de pruebas ficticias para asegurarme de que las cosas funcionaban. Con las nuevas reglas de negocios en la mano, comencé a escribir pruebas. Las pruebas identificaron rápidamente las condiciones que causaron los conflictos, y pudimos aclarar las reglas.

Si escribe pruebas que cubren la funcionalidad que está agregando o modificando, obtendrá un beneficio inmediato. Si espera una reescritura, es posible que nunca tenga pruebas automatizadas.

No debe pasar mucho tiempo escribiendo pruebas para cosas existentes que ya funcionan. La mayoría de las veces, no tiene una especificación para el código existente, por lo que lo principal que está probando es su capacidad de ingeniería inversa. Por otro lado, si va a modificar algo, debe cubrir esa funcionalidad con pruebas para saber que realizó los cambios correctamente. Y, por supuesto, para una nueva funcionalidad, escriba pruebas que fallen y luego implemente la funcionalidad que falta.

Hugh Brackett
fuente
6

Agregaré mi voz y diré que sí, ¡siempre es útil!

Sin embargo, hay algunas distinciones que debe tener en cuenta: caja negra versus caja blanca y unidad versus funcional. Como las definiciones varían, esto es lo que quiero decir con esto:

  • Black-box = pruebas que se escriben sin un conocimiento especial de la implementación, generalmente hurgando en los casos extremos para asegurarse de que las cosas sucedan como un usuario ingenuo esperaría.
  • White-box = pruebas escritas con conocimiento de la implementación, que a menudo intentan ejercer puntos de falla bien conocidos.
  • Pruebas unitarias = pruebas de unidades individuales (funciones, módulos separables, etc.). Por ejemplo: asegurándose de que su clase de matriz funcione como se esperaba, y que su función de comparación de cadenas devuelva los resultados esperados para una amplia gama de entradas.
  • Pruebas funcionales = pruebas de todo el sistema a la vez. Estas pruebas ejercitarán una gran parte del sistema a la vez. Por ejemplo: init, abrir una conexión, hacer algunas cosas del mundo real, cerrar, terminar. Me gusta hacer una distinción entre estas y las pruebas unitarias, porque tienen un propósito diferente.

Cuando agregué pruebas a un producto de envío al final del juego, descubrí que obtuve el máximo rendimiento de las pruebas de caja blanca y funcional . Si hay alguna parte del código que sabe que es especialmente frágil, escriba pruebas de recuadro blanco para cubrir los casos problemáticos y asegurarse de que no se rompa de la misma manera dos veces. Del mismo modo, las pruebas funcionales de todo el sistema son una comprobación de cordura útil que le ayuda a asegurarse de que nunca rompa los 10 casos de uso más comunes.

Las pruebas de caja negra y unidades de unidades pequeñas también son útiles, pero si su tiempo es limitado, es mejor agregarlas temprano. En el momento en que realiza el envío, generalmente ha encontrado (por las malas) la mayoría de los casos extremos y los problemas que estas pruebas habrían encontrado.

Al igual que los demás, también te recordaré las dos cosas más importantes sobre TDD:

  1. Crear pruebas es un trabajo continuo. Nunca se detiene. Debe intentar agregar nuevas pruebas cada vez que escriba un código nuevo o modificar el código existente.
  2. ¡Su conjunto de pruebas nunca es infalible! No permita que el hecho de que tenga pruebas lo lleven a una falsa sensación de seguridad. El hecho de que pase el conjunto de pruebas no significa que esté funcionando correctamente o que no haya introducido una regresión sutil de rendimiento, etc.
Drew Thaler
fuente
4

Si vale la pena agregar pruebas unitarias a una aplicación que está en producción depende del costo de mantenimiento de la aplicación. Si la aplicación tiene pocos errores y solicitudes de mejora, entonces quizás no valga la pena el esfuerzo. OTOH, si la aplicación tiene errores o se modifica con frecuencia, las pruebas unitarias serán muy beneficiosas.

En este punto, recuerde que estoy hablando de agregar pruebas unitarias selectivamente, no tratando de generar un conjunto de pruebas similares a las que existirían si hubiera practicado TDD desde el principio. Por lo tanto, en respuesta a la segunda mitad de su segunda pregunta: asegúrese de utilizar TDD en su próximo proyecto, ya sea un nuevo proyecto o una reescritura (disculpas, pero aquí hay un enlace a otro libro que realmente debería leer) : Creciente software orientado a objetos guiado por pruebas )

Mi respuesta a su tercera pregunta es la misma que la primera: depende del contexto de su proyecto.

Incrustado en su publicación hay una pregunta adicional sobre cómo garantizar que cualquier prueba retroajustada se realice correctamente . Lo importante que debe asegurarse es que las pruebas unitarias realmente sean pruebas unitarias , y esto (la mayoría de las veces) significa que las pruebas de adaptación requieren refactorizar el código existente para permitir el desacoplamiento de sus capas / componentes (cf. inyección de dependencia; inversión de control; tropezado; burlón). Si no puede aplicar esto, sus pruebas se convertirán en pruebas de integración, que son útiles, pero menos específicas y más frágiles que las pruebas unitarias reales.

Seb Rose
fuente
4

No menciona el lenguaje de implementación, pero si está en Java, entonces podría probar este enfoque:

  1. En un árbol de origen separado, construya pruebas de regresión o 'humo', utilizando una herramienta para generarlas, lo que podría brindarle una cobertura cercana al 80%. Estas pruebas ejecutan todas las rutas lógicas del código y verifican a partir de ese momento que el código sigue haciendo exactamente lo que hace actualmente (incluso si hay un error presente). Esto le brinda una red de seguridad contra el cambio inadvertido del comportamiento al realizar la refactorización necesaria para que el código sea fácilmente comprobable a mano.

  2. Para cada error que corrija, o característica que agregue de ahora en adelante, use un enfoque TDD para asegurarse de que el nuevo código esté diseñado para ser comprobable y coloque estas pruebas en un árbol de origen de prueba normal.

  3. Es probable que también sea necesario cambiar el código existente o refactorizarlo para que sea comprobable como parte de la adición de nuevas funciones; Sus pruebas de humo le proporcionarán una red de seguridad contra regresiones o cambios sutiles involuntarios en el comportamiento.

  4. Al realizar cambios (correcciones de errores o características) a través de TDD, cuando se completa es probable que la prueba de humo complementaria esté fallando. Verifique que las fallas sean las esperadas debido a los cambios realizados y elimine la prueba de humo menos legible, ya que su prueba de unidad escrita a mano tiene una cobertura completa de ese componente mejorado. Asegúrese de que su cobertura de prueba no disminuya, solo permanezca igual o aumente.

  5. Cuando corrija errores, escriba una prueba de unidad fallida que exponga primero el error.

Chris B
fuente
3

Me gustaría comenzar esta respuesta diciendo que las pruebas unitarias son realmente importantes porque lo ayudarán a detener los errores antes de que entren en producción.

Identifique las áreas de proyectos / módulos donde los errores han sido reintroducidos. Comience con esos proyectos para escribir pruebas. Tiene mucho sentido escribir pruebas para nuevas funcionalidades y para corregir errores.

¿Vale la pena el esfuerzo para una solución existente que está en producción?

Si. Verá el efecto de la disminución de errores y el mantenimiento cada vez más fácil

¿Sería mejor ignorar las pruebas para este proyecto y agregarlo en una posible reescritura futura?

Recomendaría comenzar si a partir de ahora.

Lo que será más beneficioso; pasar algunas semanas agregando pruebas o algunas semanas agregando funcionalidad?

Estás haciendo la pregunta equivocada. Definitivamente, la funcionalidad es más importante que cualquier otra cosa. Pero, más bien, debe preguntar si pasar unas semanas agregando pruebas hará que mi sistema sea más estable. ¿Esto ayudará a mi usuario final? ¿Ayudará a un nuevo desarrollador en el equipo a comprender el proyecto y también a asegurarse de que él / ella no presente un error debido a la falta de comprensión del impacto general de un cambio?

PK
fuente
3

Soy muy aficionado a Refactor the Low-hang Fruit como respuesta a la pregunta de dónde comenzar a refactorizar. Es una forma de obtener un mejor diseño sin morder más de lo que puede masticar.

Creo que la misma lógica se aplica a TDD, o simplemente a las pruebas unitarias: escriba las pruebas que necesita, según las necesite; escribir pruebas para el nuevo código; escribe pruebas de errores a medida que aparecen. Le preocupa descuidar las áreas más difíciles de alcanzar de la base del código, y ciertamente es un riesgo, pero como una forma de comenzar: ¡comience! Puede mitigar el riesgo en el futuro con herramientas de cobertura de código, y el riesgo no es (en mi opinión) tan grande, de todos modos: si está cubriendo los errores, cubriendo el nuevo código, cubriendo el código que está viendo , entonces estás cubriendo el código que tiene la mayor necesidad de pruebas.

Carl Manaster
fuente
2
  • sí lo es. cuando comienza a agregar nuevas funciones, puede causar alguna modificación del código anterior y, como resultado, es una fuente de posibles errores.
  • (vea el primero) antes de comenzar a agregar nueva funcionalidad, todo (o casi) el código (idealmente) debería estar cubierto por pruebas unitarias.
  • (ver el primero y el segundo) :). Una nueva funcionalidad grandiosa puede "destruir" el antiguo código trabajado.
Garik
fuente
2

Sí, puede: solo trate de asegurarse de que todo el código que escriba desde ahora tenga una prueba en su lugar.

Si el código que ya está en su lugar necesita ser modificado y puede probarse, entonces hágalo, pero es mejor no ser demasiado vigoroso al tratar de obtener pruebas para un código estable. Ese tipo de cosas tiende a tener un efecto de golpe y puede salir de control.

graham.reeds
fuente
2

¿Vale la pena el esfuerzo para una solución existente que está en producción?
Si. Pero no tiene que escribir todas las pruebas unitarias para comenzar. Solo agrégalos uno por uno.

¿Sería mejor ignorar las pruebas para este proyecto y agregarlo en una posible reescritura futura?
No. La primera vez que agrega código que rompe la funcionalidad, lo lamentará.

Lo que será más beneficioso; pasar algunas semanas agregando pruebas o algunas semanas agregando funcionalidad?
Para una nueva funcionalidad (código) es simple. Primero escribe la prueba unitaria y luego la funcionalidad. Para el código antiguo, usted decide el camino. No tiene que tener todas las pruebas unitarias en su lugar ... Agregue las que más le duelen sin tener ... El tiempo (y los errores) le dirán en cuál debe concentrarse;)

Tú haces
fuente
2

Actualizar

6 años después de la respuesta original, tengo una opinión ligeramente diferente.

Creo que tiene sentido agregar pruebas unitarias a todo el código nuevo que escriba, y luego refactorizar los lugares donde realiza cambios para que sean verificables.

Escribir pruebas de una sola vez para todo su código existente no ayudará, pero no escribir pruebas para el nuevo código que escriba (o las áreas que modifique) tampoco tiene sentido. Agregar pruebas a medida que refactoriza / agrega cosas es probablemente la mejor manera de agregar pruebas y hacer que el código sea más fácil de mantener en un proyecto existente sin pruebas.

Respuesta anterior

Voy a levantar algunas cejas aquí :)

En primer lugar, ¿cuál es su proyecto? Si es un compilador o un lenguaje o un marco o cualquier otra cosa que no va a cambiar funcionalmente durante mucho tiempo, entonces creo que es absolutamente fantástico agregar pruebas unitarias.

Sin embargo, si está trabajando en una aplicación que probablemente requerirá cambios en la funcionalidad (debido a los requisitos cambiantes), entonces no tiene sentido tomar ese esfuerzo adicional.

¿Por qué?

  1. Las pruebas unitarias solo cubren las pruebas de código, ya sea que el código haga lo que está diseñado para hacerlo, no es un reemplazo para las pruebas manuales que de todos modos deben hacerse (para descubrir errores funcionales, problemas de usabilidad y todo otro tipo de problemas)

  2. ¡Las pruebas unitarias cuestan tiempo! Ahora, de donde vengo, es un bien preciado, y las empresas generalmente eligen una mejor funcionalidad en lugar de un conjunto de pruebas completo.

  3. Si su aplicación es remotamente útil para los usuarios, van a solicitar cambios, por lo que tendrá versiones que harán las cosas mejor, más rápido y probablemente harán cosas nuevas, también puede haber muchas refactorizaciones a medida que su código crezca. Mantener un conjunto de pruebas de unidad completamente desarrollado en un entorno dinámico es un dolor de cabeza.

  4. Las pruebas unitarias no afectarán la calidad percibida de su producto, la calidad que ve el usuario. Claro, sus métodos podrían funcionar exactamente como lo hicieron el día 1, la interfaz entre la capa de presentación y la capa de negocios podría ser inmaculada, pero ¿adivina qué? ¡Al usuario no le importa! Obtenga algunos probadores reales para probar su aplicación. Y la mayoría de las veces, esos métodos e interfaces tienen que cambiar de todos modos, tarde o temprano.

Lo que será más beneficioso; pasar algunas semanas agregando pruebas o algunas semanas agregando funcionalidad? - Hay muchísimas cosas que puedes hacer mejor que escribir pruebas - Escribe una nueva funcionalidad, mejora el rendimiento, mejora la usabilidad, escribe mejores manuales de ayuda, resuelve errores pendientes, etc.

Ahora no me malinterpreten: si está absolutamente seguro de que las cosas no van a cambiar en los próximos 100 años, continúe, elimínelo y escriba esas pruebas. Las pruebas automatizadas también son una gran idea para las API, donde absolutamente no desea romper el código de terceros. En todas partes, ¡es algo que me hace enviar más tarde!

Roopesh Shenoy
fuente
Y le insto a que lea esto - joelonsoftware.com/items/2009/01/31.html
Roopesh Shenoy
¿Prefieres 'corregir errores pendientes' o evitar que surjan? Las pruebas unitarias adecuadas ahorran tiempo al minimizar la cantidad de tiempo dedicado a la corrección de errores.
Ihor Kaharlichenko
Eso es un mito Si me está diciendo que las pruebas unitarias automatizadas son un reemplazo para las pruebas manuales, entonces está muy, muy equivocado. ¿Y qué registran los probadores manuales, si no los errores?
Roopesh Shenoy
Y sí, no me malinterpreten: no estoy diciendo que las pruebas unitarias sean un desperdicio absoluto; el punto es considerar el tiempo que lleva escribirlas y las razones por las que podrían tener que cambiar cuando cambias tu producto, ¿realmente pagan? para mí he intentado ambos lados y la respuesta ha sido no, no me pagan lo suficientemente rápido.
Roopesh Shenoy
1

Es poco probable que alguna vez tenga una cobertura de prueba significativa, por lo que debe ser táctico sobre dónde agregar las pruebas:

  • Como mencionó, cuando encuentra un error, es un buen momento para escribir una prueba (para reproducirla) y luego corregir el error. Si ve que la prueba reproduce el error, puede estar seguro de que es una buena prueba. Dado que una porción tan grande de errores son regresiones (¿50%?), Casi siempre vale la pena escribir pruebas de regresión.
  • Cuando se sumerge en un área de código para modificarlo, es un buen momento para escribir pruebas a su alrededor. Dependiendo de la naturaleza del código, son apropiadas diferentes pruebas. Un buen conjunto de consejos se encuentra aquí .

OTOH, no vale la pena sentarse a escribir pruebas alrededor del código con el que la gente está contenta, especialmente si nadie va a modificarlo. Simplemente no agrega valor (excepto quizás entender el comportamiento del sistema).

¡Buena suerte!

ndp
fuente
1

Si estuviera en su lugar, probablemente adoptaría un enfoque de afuera hacia adentro, comenzando con pruebas funcionales que ejerciten todo el sistema. Intentaría volver a documentar los requisitos del sistema utilizando un lenguaje de especificación BDD como RSpec, y luego escribir pruebas para verificar esos requisitos mediante la automatización de la interfaz de usuario.

Luego haría un desarrollo impulsado por defectos para los errores recién descubiertos, escribiendo pruebas unitarias para reproducir los problemas y trabajando en los errores hasta que pasen las pruebas.

Para las nuevas funciones, me quedaría con el enfoque de afuera hacia adentro: comience con las funciones documentadas en RSpec y verificadas mediante la automatización de la interfaz de usuario (que por supuesto fallará inicialmente), luego agregue pruebas unitarias más finas a medida que avanza la implementación.

No soy un experto en el proceso, pero por la poca experiencia que tengo, puedo decirle que BDD a través de pruebas automatizadas de IU no es fácil, pero creo que vale la pena el esfuerzo, y probablemente le brindará el mayor beneficio en su caso.

Mhmmd
fuente
1

No soy un experto en TDD experimentado de ninguna manera, pero por supuesto diría que es increíblemente importante realizar pruebas de unidad tanto como sea posible. Dado que el código ya está en su lugar, comenzaría por implementar algún tipo de automatización de prueba unitaria. Utilizo TeamCity para ejercitar todas las pruebas en mis proyectos, y le brinda un buen resumen de cómo funcionaron los componentes.

Con eso en su lugar, pasaría a esos componentes realmente críticos de tipo empresarial que no pueden fallar. En mi caso, hay algunos problemas básicos de trigometría que deben resolverse para varias entradas, por lo que pruebo el diablo. La razón por la que hago esto es que cuando estoy quemando el aceite de medianoche, es muy fácil perder el tiempo cavando en profundidades de código que realmente no necesitan ser tocadas, porque sabes que están probadas para todas las entradas posibles (en mi caso, hay un número finito de entradas).

Ok, ahora espero que te sientas mejor con esas piezas críticas. En lugar de sentarme y golpear todas las pruebas, las atacaría a medida que surjan. Si encuentra un error que es un PITA real para solucionar, escriba las pruebas unitarias y elimínelo.

Hay casos en los que encontrará que las pruebas son difíciles porque no puede crear una instancia de una clase en particular de la prueba, por lo que debe burlarse de ella. Ah, pero tal vez no puedas burlarte de él fácilmente porque no escribiste en una interfaz. Tomo estos escenarios de "whoops" como una oportunidad para implementar dicha interfaz, porque, bueno, es algo bueno.

A partir de ahí, obtendría su servidor de compilación o cualquier automatización que tenga configurada con una herramienta de cobertura de código. Crean gráficos de barras desagradables con grandes zonas rojas donde tienes poca cobertura. Ahora, el 100% de cobertura no es su objetivo, ni el 100% de cobertura necesariamente significa que su código es a prueba de balas, pero la barra roja definitivamente me motiva cuando tengo tiempo libre. :)

Dave
fuente
1

Hay tantas buenas respuestas, así que no repetiré su contenido. Revisé tu perfil y parece que eres desarrollador de C # .NET. Debido a eso, estoy agregando referencias al proyecto Microsoft PEX y Moles que pueden ayudarlo a autogenerar pruebas unitarias para código heredado. Sé que la autogeneración no es la mejor manera, pero al menos es la forma de comenzar. Consulte este artículo muy interesante de la revista MSDN sobre el uso de PEX para el código heredado .

Ladislav Mrnka
fuente
1

Sugiero leer un artículo brillante de un ingeniero de TopTal, que explique dónde comenzar a agregar pruebas: contiene muchas matemáticas, pero la idea básica es:

1) Mida el acoplamiento aferente (CA) de su código (cuánto usan otras clases, lo que significa que romperlas causaría un daño generalizado)

2) Mida la Complejidad Ciclomática (CC) de su código (mayor complejidad = mayor cambio de ruptura)

Debe identificar las clases con CA y CC altas, es decir, tener una función f (CA, CC) y las clases con las diferencias más pequeñas entre las dos métricas deben tener la máxima prioridad para la cobertura de la prueba.

¿Por qué? Debido a que un CA alto pero muy bajo CC, las clases son muy importantes pero es poco probable que se rompan. Por otro lado, una CA baja pero una CC alta es probable que se rompan, pero causarán menos daño. Entonces quieres equilibrarte.

Andrejs
fuente
0

Depende ...
Es genial tener pruebas unitarias, pero debes considerar quiénes son tus usuarios y qué están dispuestos a tolerar para obtener un producto más libre de errores. Inevitablemente, al refactorizar su código que actualmente no tiene pruebas unitarias, introducirá errores y a muchos usuarios les resultará difícil entender que está haciendo que el producto sea temporalmente más defectuoso para hacerlo menos defectuoso a largo plazo. En última instancia, son los usuarios los que tendrán la última palabra ...

tendencia
fuente
-2

Si. No. Agregar pruebas.

Ir hacia un enfoque más TDD informará mejor sus esfuerzos para agregar nuevas funcionalidades y facilitar las pruebas de regresión. ¡Echale un vistazo!

indra
fuente