unit-testing - software - pruebas unitarias php
¿Las pruebas unitarias pueden agregarse con éxito a un proyecto de producción existente? Si es así, ¿cómo y lo vale? (23)
Estoy considerando seriamente agregar pruebas unitarias a un proyecto existente que está en producción. Comenzó hace 18 meses antes de que realmente pudiera ver algún beneficio de TDD (face palm) , por lo que ahora es una solución bastante grande con una cantidad de proyectos y no tengo ni la más mínima idea de por dónde empezar agregando pruebas unitarias. Lo que me hace considerar esto es que, de vez en cuando, un viejo error parece resurgir, o un error se registra como arreglado sin que realmente se solucione. Las pruebas unitarias reducirían o evitarían estos problemas.
Al leer preguntas similar 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 terminaré perdiendo la visión general y terminaré perdiendo pruebas fundamentales que se habrían incluido si hubiera usado TDD desde el principio.
¿Hay algún proceso / pasos que se deben seguir para garantizar que las soluciones existentes se prueban adecuadamente en unidades y no solo se abordan? ¿Cómo puedo asegurarme de que las pruebas sean de buena calidad y que no sea solo un caso de prueba es mejor que ninguna prueba ?
Así que supongo que lo que también estoy preguntando 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?
- ¿Qué será más beneficioso? ¿Pasas unas semanas añadiendo pruebas o unas semanas añadiendo funcionalidad?
(Obviamente, la respuesta al tercer punto depende completamente de si está hablando con la gerencia o un desarrollador)
Motivo de Bounty
Agregar una recompensa para tratar de atraer una gama más amplia de respuestas que no solo confirme mi sospecha de que es algo bueno que hacer, sino también algunas buenas razones en contra.
Me propongo escribir esta pregunta más adelante con pros y contras para tratar de mostrarle a la gerencia que vale la pena gastar horas para mover el futuro desarrollo del producto a TDD. Quiero abordar este desafío y desarrollar mi razonamiento sin mi propio punto de vista parcial.
- ¿Vale la pena el esfuerzo para una solución existente que está en producción?
¡Sí!
- ¿Sería mejor ignorar las pruebas para este proyecto y agregarlo en una posible reescritura futura?
¡No!
- ¿Qué será más beneficioso? ¿Pasas unas semanas añadiendo pruebas o unas semanas añadiendo funcionalidad?
Agregar pruebas (especialmente pruebas automatizadas) hace que sea mucho más fácil mantener el proyecto funcionando en el futuro, y hace que sea significativamente menos probable que envíe problemas estúpidos al usuario.
Las pruebas para poner a priori son aquellas que comprueban si lo que cree que la interfaz pública de su código (y de cada uno de sus módulos) funciona de la manera que usted piensa. Si puede, trate de inducir también cada modo de falla aislado que deben tener los módulos de código (tenga en cuenta que esto puede ser no trivial, y debe tener cuidado de no controlar demasiado cuidadosamente cómo fallan las cosas, por ejemplo, realmente no quiere para hacer cosas como contar el número de mensajes de registro producidos en caso de error, ya que es suficiente verificar que está registrado en absoluto).
Luego, ponga una prueba para cada error actual en su base de datos de errores que induce exactamente el error y que pasará cuando se solucione el error. ¡Entonces arregla esos errores! :-)
Cuesta tiempo adelantado para agregar pruebas, pero se le paga varias veces en la parte final ya que su código termina siendo de mucha más calidad. Eso es muy importante cuando intenta enviar una nueva versión o realizar un mantenimiento.
Agregaré mi voz y diré que sí, ¡siempre es útil!
Sin embargo, hay algunas distinciones que debes tener en cuenta: black-box vs white-box, y unit vs. functional. Dado que 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, por lo general hurgando en los casos extremos para garantizar que las cosas sucedan como lo esperaría un usuario ingenuo.
- White-box = pruebas que se escriben 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: asegúrese de que su clase de matriz funcione como se espera y de 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 todo 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 mayor beneficio de las pruebas funcionales y de caja blanca . Si hay alguna parte del código que usted sabe que es especialmente frágil, escriba pruebas de recuadro blanco para cubrir los casos problemáticos para asegurarse de que no se rompa de la misma manera dos veces. De manera similar, las pruebas funcionales de todo el sistema son una valiosa revisión de cordura que lo ayuda a asegurarse de nunca romper 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 antes. En el momento del envío, generalmente ha encontrado (por las malas) la mayoría de los casos extremos y problemas que estas pruebas habrían encontrado.
Al igual que los demás, también le recordaré las dos cosas más importantes sobre TDD:
- Crear pruebas es un trabajo continuo. Nunca se detiene Debería intentar agregar nuevas pruebas cada vez que escriba un nuevo código o modifique el código existente.
- ¡Su suite de prueba nunca es infalible! No permita que el hecho de que tenga pruebas lo arrulle en una falsa sensación de seguridad. El hecho de que apruebe el conjunto de pruebas no significa que esté funcionando correctamente, o que no haya introducido una regresión de rendimiento sutil, etc.
Cuando comenzamos a agregar pruebas, era una base de código de diez millones de años, aproximadamente, con demasiada lógica en la interfaz de usuario y en el código de informe.
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 por inicializar la base de datos a un estado conocido. De hecho, tenemos docenas de conjuntos de datos de regresión que guardamos en Subversion (en un repositorio separado de nuestro código, debido al tamaño). 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 luego 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, envía el informe a un archivo .csv y compara el contenido de ese .csv con una instantánea guardada. Estos snapshot .csvs se almacenan en Subversion al lado de cada conjunto de datos de regresión. Si la salida del informe no coincide con la instantánea guardada, la prueba falla.
El propósito de las pruebas de regresión es decirte si algo cambia. Eso significa que fallarán si rompes algo, pero también fallarán si cambias 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 correctos, puede haber errores en el sistema (y luego, cuando corrige esos errores, fallan las pruebas de regresió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 algunas semanas obteniendo un arnés de prueba alrededor de los informes, pudimos obtener un cierto nivel de cobertura en una gran parte de nuestra base de códigos. Escribir las pruebas unitarias equivalentes habría llevado meses o años. (Las pruebas unitarias nos hubieran dado una cobertura mucho mejor, y hubiéramos 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 solucionamos errores, añadimos mejoras o necesitábamos comprender algún 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 algún nivel de cobertura de prueba rápidamente. Luego puede comenzar a refactorizar para romper las 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. Pero al menos para nosotros, valieron la pena. Han atrapado innumerables errores en los últimos cinco años, y los atrapan en unas pocas horas, en lugar de esperar un ciclo de control de calidad. Todavía tenemos esas pruebas de regresión originales, repartidas en siete máquinas diferentes de construcción continua (distintas de la que ejecuta las pruebas de unidad rápida), e incluso las agregamos de vez en cuando, porque todavía tenemos tanto código que nuestros 6.000 + pruebas de unidad no cubren.
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, mediante la refacturación de su código, que actualmente no tiene pruebas de unidades, presentará 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 definitiva, serán los usuarios quienes tendrán la última palabra ...
El problema con la actualización de 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 estarás reescribiendo todo el componente. Si tienes tiempo para hacer esto, construirás una buena red de seguridad, pero podrías 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 forma fácil de obtenerlos allí, salvo una reescritura completa, que normalmente no se puede justificar cuando el código está funcionando y ya está ganando dinero. Recientemente, he recurrido a la escritura de scripts de PowerShell que ejercitan el código de una manera que reproduce un defecto tan pronto como se genera y luego mantienen estos scripts como un conjunto de pruebas de regresión para realizar más cambios en el futuro. 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 correctas.
Es poco probable que tengas una cobertura de prueba significativa, por lo que debes ser táctico con respecto a dónde agregar pruebas:
- Como mencionaste, cuando encuentras 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 de alid. Dado que una gran parte de los errores son regresiones (¿50%?), Casi siempre vale la pena escribir pruebas de regresión.
- Cuando te sumerges 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, diferentes pruebas son apropiadas. Un buen conjunto de consejos se encuentra aquí .
OTOH, no vale la pena sentarse a escribir pruebas sobre el código con las que la gente está contenta, especialmente si nadie va a modificarlo. Simplemente no agrega valor (excepto tal vez comprender el comportamiento del sistema).
¡Buena suerte!
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. Tiene el costo de no crear nuevas características. La forma en que estas dos cosas se equilibran depende por completo del proyecto, y hay una serie de variables.
- ¿Cuánto tiempo te tomará poner todo ese código bajo prueba? ¿Dias? ¿Semanas? ¿Meses? ¿Años?
- ¿Para quién estás escribiendo este código? Pagando a los clientes? ¿Un profesor? Un proyecto de código abierto?
- ¿Cómo es tu horario? ¿Tiene plazos difíciles que debe cumplir? ¿Tienes alguna fecha límite?
De nuevo, permítanme enfatizar que las pruebas son valiosas y que deben esforzarse por poner a prueba su antiguo código. Esto es realmente más una cuestión de cómo abordarlo. Si puede permitirse abandonar todo y poner a prueba todo su código anterior, hágalo. Si eso no es realista, esto es lo que debes hacer al menos
- Cualquier código nuevo que escriba debe estar completamente bajo prueba unitaria
- Cualquier código viejo que toque por casualidad (corrección de errores, extensión, etc.) debe someterse a pruebas unitarias.
Además, esta no es una proposición de todo o nada. Si tiene un equipo de, digamos, cuatro personas, y puede cumplir con sus plazos poniendo a una o dos personas en tareas de prueba heredadas, hágalo de todos modos.
Editar:
Me propongo escribir esta pregunta más adelante con pros y contras para tratar de mostrarle a la gerencia que vale la pena gastar horas para mover el futuro desarrollo del producto a TDD.
Esto es como preguntar "¿Cuáles son los pros y los contras del uso del control de código fuente?" o "¿Cuáles son los pros y los contras de entrevistar a las personas antes de contratarlas?" o "¿Cuáles son los pros y los contras para respirar?"
A veces solo hay un lado del argumento. Necesita tener pruebas automáticas de algún tipo para cualquier proyecto de cualquier complejidad. No, las pruebas no se escriben ellas mismas, y, sí, tomará un poco más de tiempo sacar las cosas de la puerta. Pero, a la larga, tomará más tiempo y costará más dinero arreglar los errores después del hecho que escribir pruebas por adelantado. Período. Eso es todo al respecto.
Hay tantas buenas respuestas que no repetiré su contenido. Revisé tu perfil y parece que eres desarrollador de C # .NET. Por eso, estoy agregando referencia al proyecto Microsoft PEX y Moles , que puede ayudarlo con la autogeneración de pruebas unitarias para código heredado. Sé que la autogeneración no es la mejor manera, pero al menos es la manera de comenzar. Consulte este artículo muy interesante de la revista MSDN sobre el uso de PEX para el código heredado .
Introduje pruebas unitarias para codificar bases que no lo tenían previamente. El último gran proyecto en el que participé cuando hice esto, el producto ya estaba en producción con cero pruebas de unidad cuando llegué al equipo. Cuando me fui, 2 años después, tuvimos más de 4500 pruebas que arrojaron una cobertura de código de aproximadamente 33% en una base de código con 230 000 + producción LOC (aplicación financiera Win-Forms en tiempo real). Puede parecer bajo, pero el resultado fue una mejora significativa en la calidad del código y la tasa de defectos, además de una mejora en la moral y la rentabilidad.
Se puede hacer cuando tiene tanto un entendimiento preciso y el compromiso de las partes involucradas.
En primer lugar, es importante entender que las pruebas unitarias son una habilidad en sí misma. Puede ser un programador muy productivo según los estándares "convencionales" y todavía tiene dificultades para escribir pruebas unitarias de una manera que se escala en un proyecto más grande.
Además, y específicamente para su situación, agregar pruebas unitarias a una base de códigos 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 leer el libro de Feather es un requisito (no opcional o 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 de código. Comprender esto es muy importante en términos de mentalidad y gestión de las expectativas.
Ahora, para sus comentarios y preguntas:
Sin embargo, me preocupa que terminaré perdiendo la visión general y terminaré perdiendo pruebas fundamentales que se habrían incluido si hubiera usado TDD desde el principio.
Respuesta corta: Sí, te perderás las pruebas y sí, es posible que al principio no parezcan lo que tendrían en una situación de campo verde.
La respuesta de nivel más profundo es esta: no importa. Empiezas sin pruebas. Comience a agregar pruebas y refactorice sobre la marcha. A medida que los niveles de habilidad mejoren, comience a subir la barra de todos los códigos recién escritos que se agreguen 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 excusa para no actuar". Una mejor mentalidad es enfocarse en la confianza en uno mismo. Entonces, como es posible que aún no sepa cómo hacerlo, descubrirá cómo hacerlo a medida que avanza y completará los espacios en blanco. Por lo tanto, no hay razón para preocuparse.
Nuevamente, es una habilidad. No puede pasar de cero pruebas a TDD-perfección en un enfoque de libro de cocina "proceso" o "paso a paso" de forma lineal. Será un proceso. Sus expectativas deben ser para progresar y mejorar gradualmente. No hay una pastilla magica.
La buena noticia es que a medida que pasen los meses (e incluso los años), su código comenzará a convertirse gradualmente en un código "correcto", bien integrado y bien probado.
Como nota al margen. Descubrirá que el principal obstáculo para introducir pruebas unitarias en una base de código anterior es la falta de cohesión y las dependencias excesivas. Por lo tanto, probablemente descubra que la habilidad más importante será cómo romper las dependencias existentes y desacoplar el código, en lugar de escribir las pruebas de unidades reales.
¿Hay algún proceso / pasos que se deben seguir para garantizar que las soluciones existentes se prueban adecuadamente en unidades y no solo se abordan?
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 (consulte a continuación).
Use la cobertura de código como referencia de guía de la cantidad de código de producción que está bajo prueba.
El tiempo de compilación siempre debe ser RÁPIDO. Si el tiempo de construcción es lento, las habilidades de prueba de la unidad se están rezagando. Encuentra las pruebas lentas y mejoralas (desacopla el código de producción y prueba de forma aislada). Bien escrito, deberías poder tener varios miles de pruebas unitarias y completar una construcción en menos de 10 minutos (~ 1-few ms / test es una buena pero muy aproximada pauta, algunas excepciones pueden aplicarse como código usando reflexión, etc. )
Inspeccionar y adaptar
¿Cómo puedo asegurarme de que las pruebas sean de buena calidad y que no sea solo un caso de prueba es mejor que ninguna prueba?
Tu propio juicio debe ser tu principal fuente de realidad. No hay una métrica que pueda reemplazar la habilidad.
Si no tiene esa experiencia o juicio, considere contratar a alguien que sí lo haga.
Dos indicadores secundarios aproximados son la cobertura total del código y la velocidad de compilación.
¿Vale la pena el esfuerzo para una solución existente que está en producción?
Sí. La gran mayoría del dinero gastado en un sistema o solución personalizada se gasta una vez que se pone en producción. Y invertir en calidad, personas y habilidades nunca debería 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 la vida útil esperada 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 podría haber excepciones.
¿Qué será más beneficioso? ¿Pasas unas semanas añadiendo pruebas o unas semanas añadiendo 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 de código y, como tal, requerirá tiempo. Los miembros del equipo deben aprender a romper las 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 comprender que cuando comienzas a agregar pruebas los miembros de tu equipo probablemente no lo hagan. tener estas habilidades todavía en el nivel que necesitan para que ese enfoque sea exitoso, por lo que detener el progreso para pasar todo el tiempo y 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, espera mucho aprendizaje en el camino y le pide a su patrocinador que no espere ningún ROI al detener el flujo de valor comercial. Eso no volará, y francamente no debería.
En tercer lugar, desea inculcar valores de enfoque empresarial sólidos en su equipo. La calidad nunca llega a expensas del cliente y no puede ir rápido sin calidad. Además, el cliente vive en un mundo cambiante, y su trabajo es facilitarle la adaptación. La alineación del cliente requiere calidad y flujo de valor comercial.
Lo que estás haciendo es pagar la deuda técnica. Y lo está haciendo al mismo tiempo que atiende a sus clientes siempre con necesidades cambiantes. Poco a poco, a medida que la deuda se amortiza, la situación mejora y es más fácil atender mejor al cliente y ofrecer más valor. Etc. Este impulso positivo es a lo que se debe aspirar porque subraya los principios del ritmo sostenible y mantendrá y mejorará la moral, tanto para su equipo de desarrollo, como para su cliente y sus partes interesadas.
Espero que ayude
No menciona el lenguaje de implementación, pero si está en Java, puede probar este enfoque:
En una regresión de compilación de árbol de origen independiente o pruebas de ''humo'', use una herramienta para generarlas, lo que podría acercarlo al 80% de cobertura. Estas pruebas ejecutan todas las rutas lógicas de código, y verifican a partir de ese punto que el código todavía hace exactamente lo que hace actualmente (incluso si hay un error presente). Esto le brinda una red de seguridad contra el comportamiento inadvertidamente cambiante al hacer la refactorización necesaria para que el código sea fácilmente comprobable por la unidad a mano.
- Sugerencia de producto: solía utilizar el producto gratuito basado en web Junit Factory, pero lamentablemente está cerrado ahora. Sin embargo, el producto sigue vivo en AgitarOne JUnit Generator con licencia comercial en http://www.agitar.com/solutions/products/automated_junit_generation.html
Para cada error que corrija, o una característica que agregue a partir de ahora, utilice un enfoque TDD para asegurarse de que el nuevo código esté diseñado para ser comprobado y coloque estas pruebas en un árbol de fuentes de prueba normal.
Es probable que sea necesario cambiar el código existente o cambiarlo para que sea comprobable como parte de agregar nuevas características; Sus pruebas de humo le brindarán una red de seguridad contra regresiones o cambios sutiles inadvertidos en el comportamiento.
Al realizar cambios (correcciones de errores o funciones) a través de TDD, cuando se complete es probable que la prueba de humo de la compañía falle. Verifique que las fallas sean las esperadas debido a los cambios realizados y elimine la prueba de humo menos legible, ya que la prueba de unidad escrita a mano tiene una cobertura completa de ese componente mejorado. Asegúrese de que su cobertura de prueba no disminuya, permanezca igual o aumente.
Al corregir errores, escriba una prueba de unidad que falla y que expone el error primero.
No soy un experto en TDD experimentado de ninguna manera, pero, por supuesto, diría que es increíblemente importante probar la unidad tanto como sea posible. Como el código ya está en su lugar, empezaría por implementar algún tipo de automatización de prueba de unidad. Uso TeamCity para hacer todas las pruebas en mis proyectos, y le da un buen resumen de cómo lo hicieron los componentes.
Con eso en su lugar, pasaría a aquellos componentes realmente críticos como la lógica de negocios que no pueden fallar. En mi caso, hay algunos problemas básicos de trigometría que necesitan ser resueltos para varias entradas, así que pruebo el problema de esos. La razón por la que hago esto es porque cuando estoy quemando el aceite de medianoche, es muy fácil perder tiempo cavando en profundidades de código que realmente no necesitan ser tocadas, porque sabes que están probadas para todas las posibles entradas (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 ejecutar todas las pruebas, los atacaría a medida que aparecieran. Si aciertas en un error que es un PITA real, corrige las pruebas de la unidad y quítalas del camino.
Hay casos en los que encontrará que las pruebas son difíciles porque no puede instanciar una clase particular de la prueba, por lo que tiene que burlarse de ella. Ah, pero tal vez no puedas burlarte fácilmente porque no escribiste en una interfaz. Considero estos escenarios de "ataques" como una oportunidad para implementar dicha interfaz, porque, bueno, es una buena cosa.
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 grficos de barras desagradables con grandes zonas rojas donde tienes una cobertura pobre. 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. :)
Sí. No. Agregar pruebas.
Avanzar hacia un enfoque más TDD informará mejor sus esfuerzos para agregar nuevas funcionalidades y hacer las pruebas de regresión mucho más fáciles. ¡Echale un vistazo!
Si estuviera en su lugar, probablemente tomaría un enfoque de afuera hacia adentro, empezando con pruebas funcionales que ejercitan 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 automatizando 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 trabajaré en los errores hasta que las pruebas pasen.
Para las nuevas características, me quedaría con el enfoque de afuera hacia adentro: comience con las características documentadas en RSpec y verificadas mediante la automatización de la interfaz de usuario (que por supuesto fallará inicialmente), luego agregue más pruebas unitarias de gran precisión a medida que avanza la implementación.
No soy un experto en el proceso, pero por la poca experiencia que tengo puedo decirte que BDD a través de pruebas automatizadas de UI no es fácil, pero creo que vale la pena el esfuerzo, y probablemente te rinda el mayor beneficio en tu caso.
Si vale la pena agregar pruebas unitarias a una aplicación que está en producción depende del costo de mantener la aplicación. Si la aplicación tiene pocos errores y solicitudes de mejoras, entonces tal vez 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 de manera selectiva, sin tratar 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: haga un uso de TDD en su próximo proyecto, ya sea un nuevo proyecto o una re-escritura (disculpas, pero aquí hay un enlace a otro libro que realmente debería leer : Crecimiento de 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 es una pregunta adicional sobre cómo asegurarse de que cualquier prueba retroadaptada se realice correctamente . Lo importante es asegurarse de que las pruebas unitarias sean realmente pruebas unitarias , y esto (la mayoría de las veces) significa que la actualización de pruebas requiere la refacturación del código existente para permitir el desacoplamiento de sus capas / componentes (véase inyección de dependencia, inversión de control, stubbing; burlón). Si no aplica esto, sus pruebas se convierten en pruebas de integración, que son útiles, pero menos específicas y más frágiles que las verdaderas pruebas unitarias.
Soy muy aficionado a Refactor the Low-hanging Fruit como respuesta a la pregunta de por dónde comenzar la refactorización. Es una forma de facilitar un mejor diseño sin morder más de lo que puede masticar.
Creo que la misma lógica se aplica a TDD, o solo a pruebas unitarias: escriba las pruebas que necesita, según las necesite; escribir pruebas para el nuevo código; escribir pruebas de errores a medida que aparecen. Le preocupa no tener en cuenta las áreas de código más difíciles de alcanzar, y sin duda es un riesgo, pero como una forma de comenzar: ¡comience! Puede mitigar el riesgo más adelante 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á cubriendo el código que tiene la mayor necesidad de pruebas.
Sugiero leer un article brillante de un ingeniero de TopTal, que explica 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 (la cantidad de clase utilizada por otras clases, lo que significa que romperla causaría un daño generalizado)
2) Mida la Complejidad Ciclomática (CC) de su código (mayor complejidad = mayor cambio de rompimiento)
Debe identificar las clases con alto CA y CC, es decir, tener una función f (CA, CC) y las clases con las menores diferencias entre las dos métricas deben tener la más alta prioridad para la cobertura de prueba.
¿Por qué? Porque una CA alta, pero las clases CC muy bajas son muy importantes, pero es poco probable que se rompan. Por otro lado, es probable que las CA bajas pero las CC altas se rompan, pero causarán menos daños. Entonces quieres balancear.
Usted dice que no quiere comprar otro libro. Así que solo lea el artículo de Michael Feather sobre cómo trabajar eficazmente con el código heredado . Entonces compra el libro :)
Vale la pena. Nuestra aplicación tiene complejas reglas 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 introducir pruebas unitarias automáticas y tener el marco instalado, pero no había hecho nada más que un par de pruebas ficticias para asegurarme de que todo funcionara. Con las nuevas reglas de negocio 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 nueva escritura, es posible que nunca tenga pruebas automáticas.
No deberías pasar mucho tiempo escribiendo ensayos 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 que sepa que realizó los cambios correctamente. Y, por supuesto, para una nueva funcionalidad, escriba las pruebas que fallan y luego implemente la funcionalidad faltante.
Actualizar
6 años después de la respuesta original, tengo una perspectiva ligeramente diferente.
Creo que tiene sentido agregar pruebas unitarias a todos los códigos nuevos que escriba, y luego refactorizar los lugares donde realice los cambios para que sean comprobables.
Escribir pruebas de una 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 mientras 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 extra.
¿Por qué?
Las pruebas unitarias solo cubren las pruebas de código, ya sea que el código haga para lo que está diseñado, no reemplazan las pruebas manuales que de todos modos tienen que hacerse (para descubrir errores funcionales, problemas de usabilidad y otros tipos de problemas)
¡Las pruebas unitarias cuestan tiempo! Ahora bien, de donde vengo, es un bien preciado, y las empresas generalmente escogen una mejor funcionalidad en comparación con un conjunto completo de pruebas.
Si su aplicación es incluso 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 hagan cosas nuevas, también puede haber muchas refacciones a medida que su código crezca. Mantener un conjunto de pruebas de unidades completamente desarrollado en un entorno dinámico es un dolor de cabeza.
Las pruebas unitarias no van a afectar 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 prístina, pero ¿adivinen 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.
¿Qué será más beneficioso? ¿Pasas unas semanas añadiendo pruebas o unas semanas añadiendo funcionalidad? - Hay infinidad de cosas que puedes hacer mejor que escribir pruebas: escribir nuevas funcionalidades, mejorar el rendimiento, mejorar la usabilidad, escribir mejores manuales de ayuda, resolver errores pendientes, etc., etc.
Ahora no me malinterpreten: si está absolutamente seguro de que las cosas no van a cambiar en los próximos 100 años, adelante, olvídese y escriba esas pruebas. Las Pruebas Automatizadas también son una gran idea para las API, en las que no quiere romper el código de terceros. En todos lados, ¡es algo que me hace enviar más tarde!
Me gustaría comenzar esta respuesta diciendo que las pruebas unitarias son realmente importantes porque te ayudarán a detener los errores antes de que entren en producción.
Identifique las áreas proyectos / módulos donde los errores han sido reintroducidos. Comience con esos proyectos para escribir pruebas. Tiene sentido escribir pruebas para nuevas funcionalidades y corregir errores.
¿Vale la pena el esfuerzo para una solución existente que está en producción?
Sí. Verás que el efecto de los errores disminuye y el mantenimiento se vuelve más fácil
¿Sería mejor ignorar las pruebas para este proyecto y agregarlo en una posible reescritura futura?
Yo recomendaría comenzar si a partir de ahora.
¿Qué será más beneficioso? ¿Pasas unas semanas añadiendo pruebas o unas semanas añadiendo funcionalidad?
Usted está haciendo la pregunta incorrecta. Definitivamente, la funcionalidad es más importante que cualquier otra cosa. Pero, más bien, debería preguntar si pasar unas semanas añadiendo 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 garantizar que él / ella no presente un error debido a la falta de comprensión del impacto general de un cambio?
Sí, puede: simplemente intente asegurarse de que todo el código que escriba a partir de ahora tenga una prueba en su lugar.
Si el código que ya está en su lugar necesita ser modificado y puede ser probado, 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 arrastre y puede perder el control.
¿Vale la pena el esfuerzo para una solución existente que está en producción?Sí. 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á.
¿Qué será más beneficioso? ¿Pasas unas semanas añadiendo pruebas o unas semanas añadiendo funcionalidad?Para una nueva funcionalidad (código) es simple. Primero escribe la prueba de la unidad y luego la funcionalidad. Para el código antiguo decides el camino. No tiene que tener todas las pruebas de unidad en su lugar ... Agregue las que más le duelen al no tener ... El tiempo (y los errores) le dirá en cuál debe concentrarse;)
- sí lo es. cuando comienzas a agregar nuevas funcionalidades puede causar modificaciones en el código anterior y, como resultado, es una fuente de posibles errores.
- (ver el primero) antes de comenzar a agregar nuevas funcionalidades, 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 viejo código trabajado.