Pregunta

Arruiné varias pruebas de unidad hace un tiempo cuando las revisé para hacerlas más DRY - la intención de cada prueba ya no estaba clara. Parece que hay una compensación entre la legibilidad de las pruebas y su capacidad de mantenimiento. Si dejo el código duplicado en las pruebas unitarias, son más legibles, pero luego si cambio el SUT , Tendré que localizar y cambiar cada copia del código duplicado.

¿Estás de acuerdo en que existe esta compensación? Si es así, ¿prefiere que sus pruebas sean legibles o mantenibles?

¿Fue útil?

Solución

El código duplicado es un olor en el código de prueba de unidad tanto como en otro código. Si ha duplicado el código en las pruebas, hace que sea más difícil refactorizar el código de implementación porque tiene una cantidad desproporcionada de pruebas para actualizar. Las pruebas deben ayudarlo a refactorizar con confianza, en lugar de ser una gran carga que impide su trabajo en el código que se está probando.

Si la duplicación está en la configuración del aparato, considere hacer más uso del método setUp o proporcionar más (o más flexible) Métodos de creación .

Si la duplicación está en el código que manipula el SUT, pregúntese por qué varias pruebas denominadas de "unidad" están ejerciendo exactamente la misma funcionalidad.

Si la duplicación está en las aserciones, quizás necesite algo de aserciones personalizadas . Por ejemplo, si varias pruebas tienen una cadena de aserciones como:

assertEqual('Joe', person.getFirstName())
assertEqual('Bloggs', person.getLastName())
assertEqual(23, person.getAge())

Entonces quizás necesite un solo método assertPersonEqual , para poder escribir assertPersonEqual (Person ('Joe', 'Bloggs', 23), person) . (O quizás simplemente necesite sobrecargar el operador de igualdad en Persona .)

Como usted menciona, es importante que el código de prueba sea legible. En particular, es importante que la intención de una prueba sea clara. Encuentro que si muchas pruebas parecen más o menos iguales (por ejemplo, tres cuartas partes de las líneas son iguales o prácticamente iguales) es difícil detectar y reconocer las diferencias significativas sin leerlas y compararlas cuidadosamente. Así que me parece que la refactorización para eliminar la duplicación ayuda a ser legible, porque cada línea de cada método de prueba es directamente relevante para el propósito de la prueba. Eso es mucho más útil para el lector que una combinación aleatoria de líneas que son directamente relevantes y líneas que son solo repetitivo.

Dicho esto, a veces las pruebas están ejerciendo situaciones complejas que son similares pero aún así significativamente diferentes, y es difícil encontrar una buena manera de reducir la duplicación. Use el sentido común: si cree que las pruebas son legibles y deja en claro su intención, y se siente cómodo con la necesidad de actualizar más de un número teórico mínimo de pruebas al refactorizar el código invocado por las pruebas, entonces acepte la imperfección y el movimiento A algo más productivo. ¡Siempre puedes volver y refactorizar las pruebas más tarde, cuando la inspiración te llegue!

Otros consejos

La legibilidad es más importante para las pruebas. Si una prueba falla, quiere que el problema sea obvio. El desarrollador no debería tener que vadear muchos códigos de prueba con muchos factores para determinar exactamente qué falló. No desea que su código de prueba sea tan complejo como para escribir pruebas de prueba unitaria.

Sin embargo, eliminar la duplicación suele ser algo bueno, siempre que no oculte nada, y eliminar la duplicación en sus pruebas puede llevar a una mejor API. Solo asegúrese de no superar el punto de disminución de los rendimientos.

El código de implementación y las pruebas son diferentes animales y las reglas de factorización se aplican de forma diferente a ellos.

El código o estructura duplicados siempre es un olor en el código de implementación. Cuando comience a tener repetitivo en la implementación, debe revisar sus abstracciones.

Por otro lado, el código de prueba debe mantener un nivel de duplicación. La duplicación en el código de prueba logra dos objetivos:

  • Mantener las pruebas desacopladas. El acoplamiento excesivo de la prueba puede dificultar el cambio de una sola prueba fallida que necesita actualizarse porque el contrato ha cambiado.
  • Mantener las pruebas significativas en aislamiento. Cuando una única prueba está fallando, debe ser razonablemente sencillo averiguar exactamente qué está probando.

Tiendo a ignorar la duplicación trivial en el código de prueba siempre y cuando cada método de prueba permanezca más corto que aproximadamente 20 líneas. Me gusta cuando el ritmo de configuración-ejecución-verificación es evidente en los métodos de prueba.

Cuando se duplica la duplicación en el " verificar " parte de las pruebas, a menudo es beneficioso definir métodos de afirmación personalizados. Por supuesto, esos métodos aún deben probar una relación claramente identificada que pueda hacerse evidente en el nombre del método: assertPegFitsInHole - > bueno, assertPegIsGood - > malo.

Cuando los métodos de prueba se vuelven largos y repetitivos, a veces me resulta útil definir plantillas de prueba de relleno de espacios en blanco que toman algunos parámetros. Luego, los métodos de prueba reales se reducen a una llamada al método de plantilla con los parámetros apropiados.

En cuanto a muchas cosas en la programación y las pruebas, no hay una respuesta clara. Necesita desarrollar un gusto, y la mejor manera de hacerlo es cometer errores.

Puede reducir la repetición utilizando varios tipos diferentes de métodos de utilidad de prueba .

Soy más tolerante a la repetición en el código de prueba que en el código de producción, pero a veces me siento frustrado. Cuando cambias el diseño de una clase y tienes que volver atrás y ajustar 10 métodos de prueba diferentes que hacen los mismos pasos de configuración, es frustrante.

Estoy de acuerdo. La compensación existe pero es diferente en diferentes lugares.

Es más probable que refactorice el código duplicado para configurar el estado. Pero es menos probable que refactorice la parte de la prueba que realmente ejerce el código. Dicho esto, si ejercer el código siempre toma varias líneas de código, entonces podría pensar que es un olor y refactorizar el código real bajo prueba. Y eso mejorará la legibilidad y la capacidad de mantenimiento tanto del código como de las pruebas.

Jay Fields acuñó la frase que " DSL deben ser DAMP, no DRY " ;, donde DAMP significa frases descriptivas y significativas . Creo que lo mismo se aplica a las pruebas, también. Obviamente, demasiada duplicación es mala. Pero eliminar la duplicación a toda costa es aún peor. Las pruebas deben actuar como especificaciones que revelan la intención. Si, por ejemplo, especifica la misma característica desde varios ángulos diferentes, entonces se espera una cierta cantidad de duplicación.

AMO rspec debido a esto:

Tiene 2 cosas para ayudar -

  • grupos de ejemplo compartidos para probar el comportamiento común.
    puede definir un conjunto de pruebas y luego 'incluir' ese conjunto en sus pruebas reales.

  • contextos anidados.
    Básicamente, puede tener un método de 'configuración' y 'desmontaje' para un subconjunto específico de sus pruebas, no solo para cada uno en la clase.

Cuanto antes .NET / Java / otros marcos de prueba adopten estos métodos, mejor (o podría usar IronRuby o JRuby para escribir sus pruebas, lo que personalmente creo que es la mejor opción)

No creo que haya una relación entre un código más duplicado y legible. Creo que su código de prueba debe ser tan bueno como su otro código. El código no repetitivo es más legible que el código duplicado cuando se hace bien.

Idealmente, las pruebas unitarias no deberían cambiar mucho una vez que están escritas, por lo que me inclino por la legibilidad.

Hacer que las pruebas unitarias sean lo más discretas posible también ayuda a mantener las pruebas centradas en la funcionalidad específica a la que se dirigen.

Dicho esto, tiendo a intentar y reutilizar ciertas piezas de código que termino usando una y otra vez, como el código de configuración que es exactamente el mismo en un conjunto de pruebas.

Creo que el código de prueba requiere un nivel similar de ingeniería que normalmente se aplicaría al código de producción. Ciertamente puede haber argumentos a favor de la legibilidad y estoy de acuerdo en que eso es importante.

Sin embargo, en mi experiencia, me parece que las pruebas bien estructuradas son más fáciles de leer y comprender. Si hay 5 pruebas que parecen iguales a excepción de una variable que ha cambiado y la afirmación al final, puede ser muy difícil encontrar lo que es ese elemento único diferente. De manera similar, si se factoriza de modo que solo la variable que está cambiando sea visible y la afirmación, entonces es fácil averiguar qué está haciendo la prueba inmediatamente.

Encontrar el nivel correcto de abstracción cuando las pruebas puede ser difícil y creo que vale la pena hacerlo.

" refactorizó para que estén más SECOS: la intención de cada prueba ya no estaba clara "

Parece que tuviste problemas para hacer la refactorización. Solo estoy adivinando, pero si se aclara menos, ¿no significa eso que todavía tienes más trabajo por hacer para que tengas pruebas razonablemente elegantes que estén perfectamente claras?

Es por eso que las pruebas son una subclase de UnitTest, por lo que puede diseñar buenos conjuntos de pruebas que sean correctos, fáciles de validar y borrar.

En los tiempos antiguos teníamos herramientas de prueba que usaban diferentes lenguajes de programación. Fue difícil (o imposible) diseñar pruebas agradables y fáciles de trabajar.

Tienes todo el poder de cualquier lenguaje que estés usando, Python, Java, C #, así que usa bien ese idioma. Puede lograr un código de prueba atractivo que sea claro y no demasiado redundante. No hay compensación.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top