Si su código de prueba de unidad "huele", ¿realmente importa?

52

Por lo general, solo lanzo mis pruebas unitarias usando copiar y pegar y todo tipo de malas prácticas. Las pruebas unitarias generalmente terminan pareciendo bastante feas, están llenas de "olor a código", pero ¿realmente importa? Siempre me digo a mí mismo mientras el código "real" sea "bueno", eso es todo lo que importa. Además, las pruebas unitarias generalmente requieren varios "trucos malolientes", como las funciones de troquelado.

¿Qué tan preocupado debería estar por las pruebas unitarias mal diseñadas ("malolientes")?

Botones840
fuente
8
¿Qué tan difícil puede ser arreglar el código que siempre está copiando?
Jeffo
77
El olor del código en las pruebas podría ser fácilmente un indicador de olor oculto en el resto del código. ¿Por qué abordar las pruebas como si no fueran un código "real"?
HorusKol

Respuestas:

75

¿Son importantes los olores de pruebas unitarias? Sí definitivamente. Sin embargo, son diferentes de los olores de código porque las pruebas unitarias tienen un propósito diferente y tienen un conjunto diferente de tensiones que informan su diseño. Muchos olores en el código no se aplican a las pruebas. Dada mi mentalidad TDD, en realidad argumentaría que los olores de pruebas unitarias son más importantes que los olores de código porque el código está ahí para satisfacer las pruebas.

Aquí hay algunos olores comunes de pruebas unitarias:

  • Fragilidad : ¿sus pruebas fallan a menudo e inesperadamente incluso por cambios de código aparentemente triviales o no relacionados?
  • Fuga de estado : ¿sus pruebas fallan de manera diferente dependiendo, por ejemplo, en qué orden se ejecutan?
  • Configuración / Bloqueo de desmontaje : ¿ Sus bloques de configuración / desmontaje son largos y cada vez más largos? ¿Realizan algún tipo de lógica empresarial?
  • Tiempo de ejecución lento : ¿Las pruebas tardan mucho en ejecutarse? ¿Alguna de sus pruebas unitarias individuales tarda más de una décima de segundo en ejecutarse? (Sí, lo digo en serio, una décima de segundo).
  • Fricción : ¿Las pruebas existentes dificultan la redacción de nuevas pruebas? ¿Te encuentras luchando con las fallas de las pruebas a menudo mientras refactorizas?

La importancia de los olores es que son indicadores útiles de diseño u otros temas más fundamentales, es decir, "donde hay humo, hay fuego". No solo busque olores de prueba, busque también su causa subyacente.

Aquí, por otro lado, hay algunas buenas prácticas para las pruebas unitarias:

  • Comentarios rápidos y enfocados : sus pruebas deben aislar la falla rápidamente y brindarle información útil sobre su causa.
  • Minimice la distancia del código de prueba : debe haber una ruta clara y corta entre la prueba y el código que la implementa. Las largas distancias crean bucles de retroalimentación innecesariamente largos.
  • Prueba una cosa a la vez : las pruebas unitarias solo deben probar una cosa. Si necesita probar otra cosa, escriba otra prueba.
  • Un error es una prueba que olvidó escribir : ¿Qué puede aprender de este fracaso para escribir mejores y más completas pruebas en el futuro?
Rein Henrichs
fuente
2
"Las pruebas unitarias tienen un propósito diferente y tienen un conjunto diferente de tensiones que informan su diseño". Por ejemplo, deben DAMP, no necesariamente SECO.
Jörg W Mittag
3
@ Jörg De acuerdo, pero ¿DAMP realmente representa algo? : D
Rein Henrichs
55
@Rein: frases descriptivas y significativas. Además, no completamente SECO. Ver codeshelter.wordpress.com/2011/04/07/…
Robert Harvey
+1. Tampoco sabía el significado de DAMP.
egarcia
2
@ironcode Si no puede trabajar en un sistema de forma aislada sin temor a romper su integración con otros sistemas, eso me huele a acoplamiento estrecho. Este es precisamente el propósito de identificar los olores de prueba como un tiempo de ejecución prolongado: le informan sobre problemas de diseño como el acoplamiento apretado. La respuesta no debería ser "Oh, entonces ese olor de prueba no es válido", debería ser "¿Qué me dice este olor sobre mi diseño?" Las pruebas unitarias que especifican la interfaz externa de su sistema deberían ser suficientes para decirle si sus cambios interrumpen o no la integración con sus consumidores.
Rein Henrichs
67

Estar preocupado. Escribes pruebas unitarias para demostrar que tu código actúa de la manera esperada. Le permiten refactorizar rápidamente con confianza. Si sus pruebas son frágiles, difíciles de entender o si son difíciles de mantener, ignorará las pruebas que fallan o las desactivará a medida que evoluciona su base de código, negando en primer lugar muchos de los beneficios de escribir pruebas.

jayraynet
fuente
17
+1 En el momento en que comienzas a ignorar las pruebas fallidas, no agregan ningún valor.
19

Acabo de leer The Art of Unit Testing hace un par de días. El autor aboga por poner tanto cuidado en sus pruebas unitarias como lo hace con su código de producción.

He experimentado de primera mano pruebas mal escritas e imposibles de mantener. He escrito algunos de los míos. Está prácticamente garantizado que si una prueba es difícil de mantener, no se mantendrá. Una vez que las pruebas no están sincronizadas con el código bajo prueba, se convierten en nidos de mentiras y engaños. El objetivo de las pruebas unitarias es infundir confianza en que no hemos roto nada (es decir, crean confianza). Si no se puede confiar en las pruebas, son peores que inútiles.

Joshua Smith
fuente
44
+1 tienes que mantener las pruebas, al igual que el código de producción
Hamish Smith
Otro muy buen libro sobre este tema es "Patrones de prueba xUnit de Gerard Meszaros - Código de prueba de referencia".
adrianboimvaser
7

Mientras su prueba de unidad realmente pruebe su código en "la mayoría" de los casos. (Dijo más a propósito ya que a veces es difícil encontrar todos los resultados posibles). Creo que el código "apestoso" es su preferencia personal. Siempre escribo el código de manera que pueda leerlo y comprenderlo en pocos segundos, en lugar de buscar en la basura e intentar entender qué es qué. Especialmente cuando vuelves a hacerlo después de un período de tiempo significativo.

En pocas palabras: su prueba, se supone que es fácil. No quieres confundirte con el código "maloliente".

Luke
fuente
55
+1: No te preocupes por el código de prueba de la unidad. Algunas de las preguntas más tontas giran en torno a hacer el código de prueba de unidad más "robusto" para que un cambio de programación no rompa la prueba de unidad. Tontería. Las pruebas unitarias están diseñadas para ser frágiles. Está bien si son menos robustos que el código de aplicación que están diseñados para probar.
S.Lott
1
@ S.Lott Ambos están de acuerdo y en desacuerdo, por razones mejor explicadas en mi respuesta.
Rein Henrichs
1
@Rein Henrichs: esos son olores mucho más graves que los descritos en la pregunta. "copiar y pegar" y "verse bastante feo" no suena tan mal como los olores que describe cuando las pruebas no son confiables.
S.Lott
1
@ S.Lott exactamente, creo que si vamos a hablar sobre "olores de pruebas unitarias" es crucial hacer esa distinción. Los olores de código en las pruebas unitarias a menudo no lo son. Están escritos para diferentes propósitos y las tensiones que los hacen malolientes en un contexto son muy diferentes en el otro.
Rein Henrichs
2
@ S.Lott por cierto, esta parece ser una oportunidad perfecta para usar la función de chat tan descuidada :)
Rein Henrichs
6

Seguro. Algunas personas dicen que "cualquier prueba es mejor que ninguna prueba". Estoy totalmente en desacuerdo: las pruebas mal escritas atascan su tiempo de desarrollo y terminan perdiendo días arreglando pruebas "rotas" porque, en primer lugar, no eran buenas pruebas unitarias. Para mí en este momento, las dos cosas en las que me estoy enfocando para hacer que mis pruebas sean valiosas, en lugar de una carga, son:

Mantenibilidad

Debe probar el resultado ( lo que sucede), no el método ( cómo sucede). Su configuración para la prueba debe estar tan desacoplada de la implementación como sea posible: solo configure los resultados para llamadas de servicio, etc., que sean absolutamente necesarios.

  • Use un marco burlón para asegurarse de que sus pruebas no dependan de nada externo
  • Favorece los trozos sobre los simulacros (si su marco los distingue) siempre que sea posible
  • No hay lógica en las pruebas! Los ifs, interruptores, para cada uno, casos, try-catches, etc. son todos grandes no-nos, ya que pueden introducir errores en el código de prueba en sí

Legibilidad

Está bien permitir un poco más de repetición en sus pruebas, lo que normalmente no permitiría en su código de producción, si las hace más legibles. Simplemente equilibre esto con las cosas de mantenimiento anteriores. ¡Sea explícito en lo que está haciendo la prueba!

  • Trate de mantener un estilo de "organizar, actuar, afirmar" para sus pruebas. Esto separa su configuración y expectativas del escenario, de la acción que se realiza y del resultado que se afirma.
  • Mantenga una afirmación lógica por prueba (si el nombre de su prueba contiene "y", es posible que deba dividirla en varias pruebas)

En conclusión, debe estar muy preocupado con las pruebas "malolientes": pueden terminar siendo una pérdida de tiempo, sin proporcionar ningún valor.

Usted ha dicho:

Las pruebas unitarias generalmente requieren varios "trucos malolientes", como las funciones de troquelado.

Parece que definitivamente podría hacer con la lectura de algunas técnicas de Prueba de Unidad, como el uso de un marco de Mocking, para hacer que su vida sea mucho más fácil. Recomiendo encarecidamente The Art of Unit Testing , que cubre lo anterior y mucho más. Lo encontré esclarecedor después de luchar con pruebas mal escritas, imposibles de mantener y "malolientes" durante mucho tiempo. ¡Es una de las mejores inversiones de tiempo que he hecho este año!

mjhilton
fuente
Excelente respuesta Solo tengo una pequeña objeción: mucho más importante que "una afirmación lógica por prueba" es una acción por prueba. El punto es que no importa cuántos pasos se tomen para organizar una prueba, solo debe haber una "acción de código de producción bajo prueba". Si dicha acción tuviera más de un efecto secundario, es muy posible que tenga múltiples afirmaciones.
Desilusionado
5

Dos preguntas para ti:

  • ¿Estás absolutamente seguro de que estás probando lo que crees que estás probando?
  • Si alguien más mira la prueba de la unidad, ¿podrán averiguar qué se supone que debe hacer el código ?

Hay formas de lidiar con las tareas repetitivas en las pruebas de la unidad, que suelen ser el código de configuración y desmontaje. Esencialmente, tiene un método de configuración de prueba y un método de desmontaje de prueba: todos los marcos de prueba de unidad tienen soporte para esto.

Las pruebas unitarias deben ser pequeñas y fáciles de entender. Si no lo son y la prueba falla, ¿cómo va a solucionar el problema en un período de tiempo razonablemente corto? Facilítese durante un par de meses más adelante cuando tenga que volver al código.

Berin Loritsch
fuente
5

Cuando la basura comienza a oler, es hora de sacarla. Su código de prueba debe ser tan limpio como su código de producción. ¿Se lo mostrarías a tu madre?

Bill Leeper
fuente
3

Además de las otras respuestas aquí.

El código de mala calidad en las pruebas unitarias no está restringido a su conjunto de pruebas unitarias.

Uno de los roles que cumple Unit Testing es la documentación.

El conjunto de pruebas unitarias es uno de los lugares en los que uno mira para averiguar cómo se pretende utilizar una API.

No es improbable que las personas que llaman de su API copien partes de su conjunto de pruebas unitarias, lo que lleva a que su código de conjunto de pruebas incorrecto infecte el código en vivo en otro lugar.

Carnicero paul
fuente
3

Casi no envié mi respuesta, ya que me tomó un tiempo descubrir cómo abordar esto como una pregunta legítima.

Las razones por las que los programadores se involucran en "mejores prácticas" no son por razones estéticas o porque quieren un código "perfecto". Es porque les ahorra tiempo.

  • Ahorra tiempo porque un buen código es más fácil de leer y comprender.
  • Ahorra tiempo porque cuando necesita encontrar una solución a un error, es más fácil y rápido de localizar.
  • Ahorra tiempo porque cuando desea extender el código es más fácil y rápido hacerlo.

Entonces, la pregunta que hace (desde mi punto de vista) es ¿debería ahorrarme tiempo o escribir código que me haga perder el tiempo?

A esa pregunta solo puedo decir, ¿qué tan importante es su tiempo?

FYI: los trozos, las burlas y los parches de mono tienen usos legítimos. Solo "huelen" cuando su uso no es apropiado.

dietbuddha
fuente
2

Intento específicamente NO hacer que mis pruebas de unidad sean demasiado robustas. He visto pruebas unitarias que comienzan a manejar errores en nombre de ser robusto. Lo que terminas es con pruebas que tragan los insectos que intentan atrapar. Las pruebas unitarias también tienen que hacer algunas cosas extrañas con bastante frecuencia para que las cosas funcionen. Piense en la idea completa de los accesos privados usando la reflexión ... si viera un montón de esos en el código de producción, me preocuparía 9 de cada 10 veces. Creo que debería dedicar más tiempo a pensar en lo que se está probando , en lugar de codificar la limpieza. De todos modos, las pruebas deberán cambiarse con bastante frecuencia si realizas una refactorización importante, entonces, ¿por qué no simplemente hackearlas juntas, tener un poco menos de sentido de pertenencia y estar más motivado para reescribirlas o reelaborarlas cuando llegue el momento?

Morgan Herlocker
fuente
2

Si va a buscar una cobertura pesada de bajo nivel, pasará tanto tiempo o más en el código de prueba como en el código del producto al realizar modificaciones importantes.

Dependiendo de la complejidad de la configuración de prueba, el código puede ser más complejo. (httpcontext.current vs.el horrible monstruo de tratar de construir uno falso con precisión, por ejemplo)

A menos que su producto sea algo en el que rara vez realice un cambio importante en las interfaces existentes y sus entradas de nivel de unidad sean muy simples de configurar, al menos estaría tan preocupado por la comprensión de las pruebas como el producto real.

Cuenta
fuente
0

Los olores de código son solo un problema en el código que hace que sea necesario cambiarlo o entenderlo en algún momento más adelante.

Por lo tanto, creo que debe corregir los olores de código cuando tiene que "acercarse" a la prueba unitaria dada, pero no antes.

Ian
fuente