2009-07-27 12 views
13

La mayor parte del debate en este sitio es muy positivo acerca de las pruebas unitarias. Soy un fanático de las pruebas unitarias. Sin embargo, he encontrado que las extensas pruebas unitarias traen sus propios desafíos. Por ejemplo, las pruebas unitarias a menudo se acoplan estrechamente al código que prueban, lo que puede hacer que los cambios de la API sean cada vez más costosos a medida que crece el volumen de las pruebas.¿Hay situaciones en las que las pruebas unitarias son perjudiciales para el código?

¿Ha encontrado situaciones del mundo real en las que las pruebas unitarias han sido perjudiciales para la calidad del código o el tiempo de entrega? ¿Cómo has lidiado con estas situaciones? ¿Hay alguna 'mejores prácticas' que se puedan aplicar al diseño e implementación de pruebas unitarias?

Hay una pregunta algo relacionado aquí: Why didn't unit testing work out for your project?

Respuesta

4

Con extensas pruebas de unidades, comenzará a descubrir que las operaciones de refactorización son más costosas por exactamente las razones que ha mencionado.

En mi humilde opinión esto es una buena cosa. Los cambios caros y grandes en una API deberían tener un costo mayor en relación con los cambios pequeños y baratos. Refactorizar no es una operación gratuita y es importante comprender el impacto que usted y los consumidores tienen sobre su API. Las pruebas unitarias son una excelente regla para medir qué tan caro será consumir un cambio de API.

Sin embargo, parte de este problema se alivia con las herramientas. La mayoría de los IDEs directa o indirectamente (a través de complementos) admiten operaciones de refactorización en su base de código. Usar estas operaciones para cambiar las pruebas de su unidad aliviará un poco el dolor.

+0

¿No es esto más bien un mal diseño de prueba unitaria? – txwikinger

+1

@txwikinger, no veo cómo podría ser un mal diseño. Si realiza pruebas de unidad de nivel de API y la API cambia, dará como resultado los cambios correspondientes en las pruebas de unidad. – JaredPar

+1

Bien ... las pruebas unitarias contrastan con las especificaciones. Si eso cambia, la prueba de la unidad también debe cambiar. Probablemente creo que es bueno de esa manera, ya que los cambios de la API probablemente causen roturas en otro lado. – txwikinger

1

Un exceso de falsos positivos puede retrasar el desarrollo hacia abajo, por lo que es importante para la prueba de lo que realmente quiere permanecer invariable. Por lo general, esto significa escribir pruebas unitarias con anticipación para los requisitos, y luego hacer un seguimiento con pruebas unitarias más detalladas para detectar cambios inesperados en la producción.

1

Principalmente en los casos en que el sistema se desarrolló sin tener en cuenta las pruebas de unidad, fue una idea de último momento y no una herramienta de diseño. Cuando desarrollas con pruebas automatizadas, las posibilidades de romper tu API disminuyen.

+0

¿Hay algún buen diseño que tenga problemas con las pruebas unitarias? – txwikinger

0

Si está seguro de que su código no se reutilizará, no será necesario mantenerlo, su proyecto es simple y de muy corto plazo; entonces no deberías necesitar pruebas unitarias.

Las pruebas unitarias son útiles para facilitar los cambios y el mantenimiento. Realmente agregan un poco a tiempo a la entrega, pero se paga a mediano/largo plazo. Si no hay medio/largo plazo, puede ser innecesario, ya que las pruebas manuales son suficientes.

Pero todo esto es muy poco probable, sin embargo. Por lo que siguen siendo una tendencia :)

También, a veces puede ser una decisión de negocios necesario invertir menos tiempo en las pruebas, con el fin de tener una entrega más rápida de urgencia (que tendrá que ser pagado con intereses más adelante)

+2

Las pruebas unitarias mejoran el código incluso si el código no se va a cambiar en el futuro. – dss539

+0

@ dss539 Pero si el código no se va a modificar, ya no es necesario que sea legible, bien diseñado, etc. –

+4

no, no es necesario que sea legible y esté bien diseñado para garantizar que funcionó correctamente en primer lugar . las pruebas unitarias son una gran ayuda en el diseño y también pueden detectar algunos errores. El hecho de que una pieza de código nunca vaya a cambiar no significa que pueda ser una mierda. las pruebas unitarias ayudan a minimizar la posibilidad de que un código dado sea una mierda – dss539

0

Sí, existen situaciones en las que las pruebas unitarias pueden ser perjudiciales para la calidad del código y el tiempo de entrega. Si creas demasiadas pruebas de unidad, tu código se destrozará con las interfaces y la calidad de tu código como un todo sufrirá. La abstracción es genial, pero puedes tener demasiada.

Si su unidad de escritura prueba un prototipo o un sistema que tiene una alta probabilidad de tener cambios importantes, la prueba de su unidad tendrá un efecto en el tiempo de entrega. En estos casos, a menudo es mejor escribir la prueba de aceptación que prueba más de punta a punta.

+0

¿Por qué tendrías que crear tantas interfaces? Hay más de una forma de inyectar dependencias ... ¿o estás hablando de otra cosa? – dss539

+0

Hmm, mi opinión puede provenir simplemente de haber tenido que agregar pruebas unitarias a proyectos existentes. Me encantaría usar TDD desde el principio en algo. – gradbot

+1

Sí, hay formas no intrusivas de inyectar dependencias, pero algunas personas están obsesionadas con desacoplar y abstraer todo excesivamente, a menudo en nombre de la capacidad de prueba. –

0

Las pruebas de unidades lentas a menudo pueden ser perjudiciales para el desarrollo.Esto generalmente ocurre cuando las pruebas unitarias se convierten en pruebas de integración que deben afectar los servicios web o la base de datos. Si su conjunto de pruebas de unidad demora más de una hora en ejecutarse, muchas veces se encontrará usted y su equipo esencialmente paralizados por esa hora esperando ver si las pruebas de la unidad pasan o no (dado que no desea seguir construyendo sobre un base rota).

Dicho esto, creo que los beneficios ahora superan los inconvenientes en todos los casos menos en los más artificiales.

2

Uno de los proyectos en los que trabajé fue fuertemente probado en unidades; tuvimos más de 1000 pruebas unitarias para aproximadamente 20 clases. Hubo un poco más de código de prueba que código de producción. Las pruebas unitarias detectaron innumerables errores introducidos durante las operaciones de refactorización; definitivamente hicieron que sea fácil y seguro realizar cambios, ampliar la funcionalidad, etc. El código publicado tenía una tasa de errores muy baja.

Para animarnos a escribir las pruebas unitarias, elegimos específicamente mantenerlas 'rápidas y sucias' - probamos una prueba cuando produjimos el código del proyecto, y las pruebas fueron aburridas y 'código no real', así que tan pronto como escribimos uno que ejerció la funcionalidad del código de producción, terminamos y continuamos. El único criterio para el código de prueba fue que ejerció completamente la API del código de producción.

Lo que aprendimos de la manera difícil es que este enfoque no escala. A medida que el código evolucionó, vimos la necesidad de cambiar el patrón de comunicación entre nuestros objetos, ¡y de repente tuve 600 pruebas unitarias fallidas! Reparar esto me tomó varios días. Este nivel de rotura de prueba ocurrió dos o tres veces con refactorizaciones de arquitectura más importantes. En cada caso, no creo que podamos razonablemente haber previsto la evolución del código que se requería de antemano.

La moraleja de la historia para mí fue esta: código de prueba de unidad debe ser tan limpio como el código de producción. Simplemente no puede salirse con la tarea de cortar y pegar en pruebas unitarias. Debe aplicar una refactorización razonable y desacoplar las pruebas del código de producción cuando sea posible mediante el uso de objetos proxy.

Por supuesto, todo esto agrega complejidad y costo a las pruebas de su unidad (¡y puede introducir errores en sus pruebas!), Por lo que es un buen equilibrio. Pero sí creo que el concepto de "pruebas unitarias", tomado en forma aislada, no es la victoria clara e inequívoca que a menudo se supone que es. Mi experiencia es que las pruebas unitarias, como todo lo demás en programación, requieren cuidado y no son una metodología que se pueda aplicar a ciegas. Por lo tanto, es sorprendente para mí que no haya visto más discusión sobre este tema en foros como este y en la literatura.

+1

"que este enfoque no escala" Conclusión incorrecta. La conclusión correcta es "Los cambios API son costosos y complicados" independientemente del volumen de las pruebas. El enfoque escala y las pruebas revelaron el alcance y la complejidad de los cambios. –

3

¿Hay 'mejores prácticas', que se pueden aplicar al diseño y ejecución de pruebas unitarias?

asegurarse de que su unidad de pruebas no se han hecho pruebas de integración.Por ejemplo, si tiene pruebas unitarias para una clase Foo, a continuación, idealmente las pruebas sólo puede romperse si

  1. hubo un cambio en Foo
  2. o se ha producido un cambio en las interfaces utilizadas por los Foo
  3. o hubo un cambio en el modelo de dominio (por lo general tendrá algunas clases como "Customer", que son fundamentales para el espacio del problema, no tienen espacio para la abstracción y por lo tanto no se ocultan detrás de una interfaz)

Si su las pruebas están fallando debido a cualquier otro cambio, luego th Se han convertido en pruebas de integración y te meterás en problemas a medida que el sistema crezca. Las pruebas unitarias no deberían tener tales problemas de escalabilidad porque prueban una unidad aislada del código.

1

Creo que está buscando solucionar un síntoma, en lugar de reconocer todo el problema. El problema principal es que una verdadera API es una interfaz publicada *, y debe estar sujeta a los mismos límites que usted colocaría en cualquier contrato de programación: ¡sin cambios! Puede agregar a una API y llamarla API v2, pero no puede volver atrás y cambiar API v1.0; de lo contrario, ha roto la compatibilidad con versiones anteriores, lo que casi siempre es malo para una API.

(* No me refiero a llamar a cualquier tecnología de interfaz específica o el idioma, la interfaz puede significar cualquier cosa de las declaraciones de clases o más.)

Yo sugeriría que un enfoque de Desarrollo Test Driven ayudaría a prevenir muchos de este tipo de problemas en primer lugar. Con TDD estarías "sintiendo" la incomodidad de las interfaces mientras escribías las pruebas, y te verías obligado a arreglar esas interfaces más temprano en el proceso en lugar de esperar hasta después de haber escrito miles de pruebas.

Uno de los principales beneficios de Test Driven Development es que le proporciona información instantánea sobre el uso programático de su clase/interfaz. El acto de escribir una prueba es una prueba de su diseño, mientras que el acto de ejecutar la prueba es la prueba de su comportamiento. Si es difícil o incómodo escribir una prueba para un método en particular, entonces es probable que ese método se use incorrectamente, lo que significa que es un diseño débil y debe refactorizarse rápidamente.

Cuestiones relacionadas