2008-09-24 15 views
79

He arruinado varias pruebas unitarias hace algún tiempo cuando las revisé y las refactoricé para hacerlas más DRY - la intención de cada prueba ya no estaba clara. Parece que hay una compensación entre la legibilidad y la capacidad de mantenimiento de las pruebas. Si dejo el código duplicado en pruebas unitarias, son más legibles, pero si cambio el SUT, tendré que buscar y cambiar cada copia del código duplicado.¿El código duplicado es más tolerable en las pruebas unitarias?

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

Respuesta

47

El código duplicado es un olor en el código de prueba de la unidad tanto como en otros códigos. Si tiene código duplicado en las pruebas, es más difícil refactorizar el código de implementación porque tiene una cantidad desproporcionada de pruebas para actualizar.Las pruebas deberían ayudarlo a refactorizarse con confianza, en lugar de ser una gran carga que le impida trabajar en el código que se prueba.

Si la duplicación está en la configuración del dispositivo, considere hacer más uso del método setUp o proporcionar más (o más flexible) Creation Methods.

Si la duplicación está en el código que manipula el SUT, pregúntese por qué las llamadas pruebas de "unidades" múltiples ejercen exactamente la misma funcionalidad.

Si la duplicación está en las aserciones, entonces tal vez necesite alguna Custom Assertions. Por ejemplo, si varias pruebas tienen una serie de afirmaciones como:

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

Entonces, tal vez necesite un solo método assertPersonEqual, por lo que se puede escribir assertPersonEqual(Person('Joe', 'Bloggs', 23), person). (O tal vez simplemente necesite sobrecargar el operador de igualdad en Person.)

Como 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 se ven 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 cuidadosamente y compararlas. Así que me parece que la refactorización para eliminar la duplicación ayuda a la legibilidad de, 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 simplemente repetitivas.

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

+11

"El código duplicado es un olor en el código de prueba de la unidad tanto como en otros códigos". No. "Si ha duplicado el código en las pruebas, es más difícil refactorizar el código de implementación porque tiene una cantidad desproporcionada de pruebas para actualizar". Esto sucede porque estás probando la API privada en lugar de la API pública. –

+4

Pero para prevenir el código duplicado en las pruebas unitarias, generalmente necesita introducir una nueva lógica. No creo que las pruebas unitarias contengan lógica porque entonces necesitarías pruebas unitarias de pruebas unitarias. –

133

La legibilidad es más importante para las pruebas. Si falla una prueba, quiere que el problema sea obvio. El desarrollador no debería tener que pasar por una gran cantidad de código de prueba con muchos factores para determinar exactamente qué falló. No desea que su código de prueba se vuelva tan complejo que necesite escribir pruebas de prueba unitarias.

Sin embargo, la eliminación de la duplicación suele ser una buena cosa, siempre que no oscurezca nada, y la eliminación de la duplicación en las pruebas puede conducir a una mejor API. Solo asegúrate de no ir más allá del punto de rendimientos decrecientes.

+0

xUnit y otros contienen un argumento 'mensaje' en llamadas afirmativas.Buena idea para poner frases significativas para permitir a los desarrolladores encontrar rápidamente resultados de prueba fallidos. – seand

+1

@seand Puede intentar explicar lo que su afirmación está comprobando, pero cuando está fallando y contiene un código algo oscurecido, el desarrollador deberá ir y relajarse de todos modos. IMO Es más importante tener código para autodescribir allí. – IgorK

+0

@Kristopher, ¿? ¿Por qué está publicado esto es wiki de la comunidad? – Pacerier

1

No creo que exista una relación entre más código duplicado y legible. Creo que tu código de prueba debería ser tan bueno como tu otro código. El código que no se repite es más legible que el código duplicado cuando se hace bien.

2

Idealmente, las pruebas unitarias no deberían cambiar mucho una vez que se escriben, por lo que me inclinaría por la legibilidad.

Tener pruebas unitarias sea lo más discreto 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.

6

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 el ejercicio del 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.

+0

Creo que esta es una buena idea. Si tiene mucha duplicación, vea si puede refactorizar para crear un "accesorio de prueba" común bajo el cual se pueden ejecutar muchas pruebas. Esto eliminará el código duplicado de instalación/desmontaje. –

32

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

El código duplicado o la estructura son siempre un olor en el código de implementación. Cuando empiezas a tener un modelo repetitivo en implementación, necesitas revisar tus 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:

  • Pruebas de mantenimiento desacopladas. Un acoplamiento de prueba excesivo puede dificultar el cambio de una sola prueba de falla que debe actualizarse porque el contrato ha cambiado.
  • Manteniendo las pruebas significativas de forma aislada. Cuando una sola 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 unas 20 líneas.Me gusta cuando el ritmo de setup-run-verify es evidente en los métodos de prueba.

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

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

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

6

Puede reducir la repetición usando varios sabores diferentes de test utility methods.

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

2

"ellos refactorizado para hacerlos más seco - la intención de cada prueba ya no estaba claro"

Suena como que tenía problemas para hacer la refactorización. Solo estoy adivinando, pero si terminó menos claro, ¿no significa eso que aún 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 buenas suites de prueba que sean correctas, fáciles de validar y borrar.

En la antigüedad teníamos herramientas de prueba que utilizaban diferentes lenguajes de programación. Fue difícil (o imposible) diseñar pruebas agradables y fáciles de trabajar.

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

3

AMO rspec debido a esto:

Tiene 2 cosas para ayudar -

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

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

Cuanto antes que .NET/Java/otros marcos de prueba adoptan estos métodos, la mejor (o puede utilizar IronRuby o JRuby para escribir sus ensayos, que yo personalmente creo que es la mejor opción)

3

Jay Los campos que acuñó la frase de que "DSL debe estar húmedo, no seco", 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 reveladoras de intenciones. Si, por ejemplo, especifica la misma característica desde varios ángulos diferentes, entonces se espera una cierta cantidad de duplicación.

1

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, se pueden hacer argumentos a favor de la legibilidad y estoy de acuerdo en que es importante.

En mi experiencia, sin embargo, me parece que las pruebas bien factorizadas son más fáciles de leer y comprender. Si hay 5 pruebas que tienen el mismo aspecto, excepto una variable que se modificó y la afirmación al final, puede ser muy difícil encontrar cuál es el único elemento que difiere. De manera similar, si se factoriza de modo que solo la variable que está cambiando sea visible y la aserción, entonces es fácil descubrir qué hace la prueba de inmediato.

Encontrar el nivel adecuado de abstracción cuando las pruebas pueden ser difíciles y creo que vale la pena hacerlo.

Cuestiones relacionadas