¿Cómo facilitan el diseño las pruebas unitarias?

43

Nuestro colega promueve la escritura de pruebas unitarias, ya que en realidad nos ayuda a refinar nuestro diseño y refactorizar las cosas, pero no veo cómo. Si estoy cargando un archivo CSV y lo analizo, ¿cómo me ayudará la prueba unitaria (validando los valores en los campos) para verificar mi diseño? Mencionó el acoplamiento y la modularidad, etc., pero para mí no tiene mucho sentido, pero no tengo muchos antecedentes teóricos, sin embargo.

Eso no es lo mismo que la pregunta que marcó como duplicado, me interesaría ver ejemplos reales de cómo esto ayuda, no solo la teoría que dice "ayuda". Me gusta la respuesta a continuación y el comentario, pero me gustaría obtener más información.

    
pregunta User039402 09.02.2017 - 19:12

10 respuestas

2

Las pruebas unitarias no solo facilitan el diseño, sino que es uno de sus beneficios clave.

La escritura de prueba primero elimina la modularidad y limpia la estructura del código.

Cuando escriba su código de prueba en primer lugar, encontrará que cualquier "condición" de una unidad de código dada se elimina naturalmente a las dependencias (generalmente a través de simulacros o talones) cuando las asume en su código.

"Dada la condición x, espere el comportamiento y", a menudo se convertirá en un apéndice para suministrar x (que es un escenario en el que la prueba debe verificar el comportamiento del componente actual) y y se convertirá en un simulacro, una llamada a la que se verificará al final de la prueba (a menos que sea un "debería devolver y ", en cuyo caso la prueba solo verificará el valor de retorno explícitamente).

Luego, una vez que esta unidad se comporta como se especifica, pasas a escribir las dependencias (para x y y ) que has descubierto.

Esto hace que la escritura de código limpio y modular sea un proceso muy fácil y natural, donde de lo contrario a menudo es fácil confundir responsabilidades y unir comportamientos sin darse cuenta.

Escribir exámenes más tarde le dirá cuándo su código está mal estructurado.

Cuando escribir pruebas para un fragmento de código se vuelve difícil porque hay demasiadas cosas para tachar o simular, o porque las cosas están demasiado juntas, sabes que debes hacer mejoras en tu código.

Cuando el "cambio de pruebas" se convierte en una carga porque hay muchos comportamientos en una sola unidad, sabe que debe hacer mejoras en su código (o simplemente en su enfoque para escribir pruebas, pero este no suele ser el caso en mi experiencia).

Cuando tus escenarios se vuelven demasiado complicados ("si x y y y z entonces ...") porque necesitas abstraer más, sabes que debes hacer mejoras en tu código.

Cuando terminas con las mismas pruebas en dos dispositivos diferentes debido a la duplicación y la redundancia, sabes que tienes que hacer mejoras en tu código.

Aquí hay una excelente charla de Michael Feathers que demuestra la estrecha relación entre la capacidad de prueba y el diseño en código (originalmente publicado por displayName en los comentarios). La charla también aborda algunas quejas comunes y conceptos erróneos sobre el buen diseño y la comprobabilidad en general.

    
respondido por el Ant P 11.02.2017 - 10:45
103

Lo mejor de las pruebas unitarias es que le permiten usar su código de la manera en que otros programadores lo usarán.

Si su código es incómodo para la prueba de unidad, entonces probablemente será difícil de usar. Si no puede inyectar dependencias sin saltar a través de aros, entonces su código probablemente será inflexible de usar. Y si necesita pasar mucho tiempo configurando datos o averiguar en qué orden hacer las cosas, su código bajo prueba probablemente tenga demasiados problemas y será un dolor trabajar con ellos.

    
respondido por el Telastyn 09.02.2017 - 19:15
31

Me tomó bastante tiempo darme cuenta, pero el beneficio real (edit: para mí, su kilometraje puede variar) de hacer un desarrollo guiado por pruebas ( usando pruebas unitarias) es que usted ¡Tengo que hacer el diseño de la API por adelantado !

Un enfoque típico del desarrollo es descubrir primero cómo resolver un problema dado, y con ese conocimiento y diseño de implementación inicial, hay alguna forma de invocar su solución. Esto puede dar algunos resultados bastante interesantes.

Al hacer TDD, debes escribir el código que usará tu solución. Ingrese los parámetros y la salida esperada para que pueda asegurarse de que es correcto. Eso, a su vez, requiere que averigües qué es lo que realmente necesitas para que lo haga, para que puedas crear pruebas significativas. Entonces y solo entonces implementas la solución. También es mi experiencia que cuando sabes exactamente lo que se supone que tu código debe lograr, se vuelve más claro.

Luego, después de la implementación, las pruebas unitarias lo ayudan a asegurarse de que la refactorización no interrumpa la funcionalidad y proporcione documentación sobre cómo usar su código (¡lo cual sabe que es correcto cuando se aprobó la prueba!). Pero estos son secundarios: el mayor beneficio es la mentalidad de crear el código en primer lugar.

    
respondido por el Thorbjørn Ravn Andersen 10.02.2017 - 00:09
6

Estoy de acuerdo con el 100% de que las pruebas unitarias ayudan "a ayudarnos a refinar nuestro diseño y refactorizar las cosas".

Tengo dos mentes sobre si te ayudan a hacer el diseño inicial . Sí, revelan defectos obvios y te obligan a pensar en "¿Cómo puedo hacer que el código sea verificable"? Esto debería llevar a menos efectos secundarios, configuraciones y configuraciones más fáciles, etc.

Sin embargo, en mi experiencia, las pruebas unitarias demasiado simplistas, escritas antes de que realmente entiendas lo que debería ser el diseño (es cierto que es una exageración de TDD de núcleo duro, pero los programadores escriben una prueba antes de que piensen mucho). conducen a modelos de dominio anémicos que exponen demasiados elementos internos.

Mi experiencia con TDD fue hace varios años, por lo que me interesa escuchar qué nuevas técnicas pueden ayudar a escribir pruebas que no sesguen demasiado el diseño subyacente. Gracias.

    
respondido por el user949300 10.02.2017 - 01:20
5

La prueba unitaria le permite ver cómo funcionan las interfaces entre las funciones y, a menudo, le proporciona información sobre cómo mejorar tanto el diseño local como el diseño general. Además, si desarrolla sus pruebas de unidad mientras desarrolla su código, tiene un conjunto de pruebas de regresión listo. No importa si está desarrollando una interfaz de usuario o una biblioteca de fondo.

Una vez que se desarrolla el programa (con pruebas unitarias), a medida que se descubren los errores, puede agregar pruebas para confirmar que los errores se han solucionado.

Yo uso TDD para algunos de mis proyectos. Pongo mucho esfuerzo en la elaboración de ejemplos que extraigo de libros de texto o de artículos que se consideran correctos, y pruebo el código que estoy desarrollando usando este ejemplo. Cualquier malentendido que tenga con respecto a los métodos se vuelve muy evidente.

Tiendo a ser un poco más relajado que algunos de mis colegas, ya que no me importa si el código se escribe primero o la prueba se escribe primero.

    
respondido por el Robert Baron 09.02.2017 - 19:57
5

Cuando desee realizar una prueba unitaria de su analizador, el valor de detección se delimita correctamente, es posible que desee pasar una línea desde un archivo CSV. Para que su prueba sea directa y breve, puede probarla a través de un método que acepte una línea.

Esto hará que separe automáticamente la lectura de líneas de la lectura de valores individuales.

En otro nivel, es posible que no desee colocar todo tipo de archivos CSV físicos en su proyecto de prueba, sino hacer algo más legible, simplemente declarando una cadena CSV grande dentro de su prueba para mejorar la legibilidad y la intención de la prueba. Esto lo llevará a desacoplar su analizador de cualquier E / S que haría en otro lugar.

Solo un ejemplo básico, solo comienza a practicarlo, sentirás la magia en algún momento (tengo).

    
respondido por el Joppe 09.02.2017 - 20:28
3

En pocas palabras, las pruebas unitarias de escritura ayudan a exponer fallas en su código.

Esta espectacular guía de para escribir código comprobable , escrita por Jonathan Wolter, Russ Ruffer Y Miško Hevery contiene numerosos ejemplos de cómo las fallas en el código, que inhiben las pruebas, también impiden la reutilización fácil y la flexibilidad del mismo código. Por lo tanto, si su código es verificable, es más fácil de usar. La mayoría de las "moralejas" son consejos ridículamente simples que mejoran enormemente el diseño del código ( Inyección de dependencia FTW).

Por ejemplo: es muy difícil probar si el método computeStuff funciona correctamente cuando el caché comienza a desalojar cosas. Esto se debe a que tiene que agregar manualmente la basura al caché hasta que "bigCache" esté casi lleno.

public OopsIHardcoded {

   Cache cacheOfExpensiveComputations;

   OopsIHardcoded() {
       this.cacheOfExpensiveComputation = buildBigCache();
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Sin embargo, cuando utilizamos la inyección de dependencia, es mucho más fácil probar si el método computeStuff funciona correctamente cuando el caché comienza a desalojar cosas. Todo lo que hacemos es crear una prueba en la que llamamos new HereIUseDI(buildSmallCache()); Note, tenemos un control más matizado del objeto y paga dividendos de inmediato.

public HereIUseDI {

   Cache cacheOfExpensiveComputations;

   HereIUseDI(Cache cache) {
       this.cacheOfExpensiveComputation = cache;
   }

   ExpensiveValue computeStuff() {
      //DOES THIS WORK CORRECTLY WHEN CACHE EVICTS DATA?
   }
}

Se pueden obtener beneficios similares cuando nuestro código requiere datos que generalmente se encuentran en una base de datos ... simplemente pase EXACTAMENTE los datos que necesita.

    
respondido por el Ivan 09.02.2017 - 21:41
0

Dependiendo de lo que se entiende por 'Pruebas de unidad', no creo que las pruebas de Unidad realmente de bajo nivel faciliten un buen diseño sino que las pruebas de integración de nivel ligeramente superior - pruebas que prueban que un grupo de actores (clases, funciones, lo que sea) en su código se combinan adecuadamente para producir un montón de comportamientos deseables que han sido acordados entre el equipo de desarrollo y el propietario del producto.

Si puedes escribir pruebas en esos niveles, te empuja a crear un código lógico y similar a API que no requiera muchas dependencias locas: el deseo de tener una configuración de prueba simple naturalmente te llevará a no tiene muchas dependencias locas o código estrechamente acoplado.

Sin embargo, no se equivoque, las pruebas unitarias pueden llevarlo a un mal diseño, así como a un buen diseño . He visto a desarrolladores que toman un poco de código que ya tiene un diseño lógico agradable y una única preocupación, y lo desarman e introducen más interfaces con el único fin de realizar pruebas, y como resultado hacen que el código sea menos legible y más difícil de cambiar. , y posiblemente incluso tener más errores si el desarrollador ha decidido que realizar muchas pruebas unitarias de bajo nivel significa que no es necesario que se realicen pruebas de mayor nivel. Un ejemplo favorito en particular es un error que solucioné en el que había una gran cantidad de código "comprobable" muy descompuesto relacionado con la obtención y el acceso de información al portapapeles. Todos desglosados y desacoplados a muy pequeños niveles de detalle, con muchas interfaces, muchos simulacros en las pruebas y otras cosas divertidas. Solo un problema: no hubo ningún código que interactuara realmente con el mecanismo del portapapeles del sistema operativo, por lo que el código en producción en realidad no hizo nada.

Las pruebas unitarias definitivamente pueden impulsar tu diseño, pero no te guían automáticamente a un buen diseño. Necesitas tener ideas sobre qué es un buen diseño que va más allá de 'este código' probado, por lo tanto es comprobable, por lo tanto es bueno ".

Por supuesto, si usted es una de esas personas para quienes 'pruebas de unidad' significa 'cualquier prueba automatizada que no se conduzca a través de la interfaz de usuario', entonces algunas de esas advertencias pueden no ser tan relevantes, como dije, Creo que esas pruebas de integración de alto nivel suelen ser las más útiles cuando se trata de impulsar su diseño.

    
respondido por el topo morto 11.02.2017 - 18:15
-2

Las pruebas unitarias pueden ayudar con la refactorización cuando el código nuevo pasa todas las pruebas antiguas .

Supongamos que ha implementado un bubblesort porque tenía prisa y no le preocupaba el rendimiento, pero ahora desea un quicksort porque los datos se hacen más largos. Si todas las pruebas pasan, las cosas se ven bien.

Por supuesto, las pruebas deben ser exhaustivas para que esto funcione. En mi ejemplo, es posible que sus pruebas no cubran estabilidad porque eso no le preocupa a bubblesort.

    
respondido por el o.m. 11.02.2017 - 13:16
-3

He encontrado que las pruebas unitarias son las más valiosas para facilitar el mantenimiento a largo plazo de un proyecto. Cuando vuelvo a un proyecto después de meses y no recuerdo muchos de los detalles, ejecutar pruebas me impide romper cosas.

    
respondido por el David Baker 10.02.2017 - 00:45

Lea otras preguntas en las etiquetas