Cómo hacer desarrollo guiado por pruebas


15

Tengo solo más de 2 años de experiencia en el desarrollo de aplicaciones. En esos dos años mi enfoque hacia el desarrollo fue el siguiente

  1. Analizar requisitos
  2. Componentes / objetos de Identity Core, funciones requeridas, comportamiento, proceso y sus restricciones
  3. Crear clases, relación entre ellas, restricciones en el comportamiento y estados de los objetos.
  4. Crear funciones, procesar con restricciones de comportamiento según los requisitos
  5. Aplicación de prueba manual
  6. Si los cambios en los requisitos modifican componentes / funciones, pruebe manualmente la aplicación

Recientemente me presentaron a TDD y siento que esta es una muy buena manera de hacer el desarrollo, ya que el código desarrollado tiene una fuerte razón para existir y se mitigan muchos problemas posteriores a la implementación.

Pero mi problema es que no puedo crear pruebas primero, sino que estoy identificando componentes y simplemente escribiendo pruebas para ellos antes de escribir los componentes. mi pregunta es

  1. ¿Lo estoy haciendo bien? Si no es exactamente lo que tengo que cambiar
  2. ¿Hay alguna forma de identificar si la prueba que ha escrito es suficiente?
  3. ¿Es una buena práctica escribir una prueba para una funcionalidad muy simple que podría ser equivalente a 1 + 1 = 2 o es solo una exageración?
  4. ¿Es bueno cambiar la funcionalidad y probar si los requisitos cambian?

2
"Estoy identificando componentes y simplemente escribo una prueba para ellos antes de que realmente escriba los componentes". Me parece correcto: primero identifica la arquitectura gruesa de su sistema y luego comienza a codificar. Durante la codificación (TDD), resuelve los detalles de los componentes individuales y posiblemente descubre problemas con su arquitectura que puede solucionar en el camino. Pero me parece bien que no comiences a codificar sin ningún análisis previo.
Giorgio

También podría considerar realizar pruebas automatizadas de unidad / integración sin hacer TDD. Los dos a menudo están confundidos, pero no son lo mismo.
Andres F.

Respuestas:


19

¿Lo estoy haciendo bien? Si no es exactamente lo que tengo que cambiar

Es difícil decir a partir de ese breve descripción, pero sospecho que no, usted está no haciendo bien. Nota: No estoy diciendo que lo que está haciendo no funciona o de alguna manera es malo, pero no está haciendo TDD. La "D" central significa "Impulsado", las pruebas conducen todo, el proceso de desarrollo, el código, el diseño, la arquitectura, todo .

Las pruebas le dicen qué escribir, cuándo escribirlo, qué escribir a continuación, cuándo dejar de escribir. Te cuentan el diseño y la arquitectura. (El diseño y la arquitectura emergen del código a través de la refactorización). TDD no se trata de pruebas. Ni siquiera se trata de escribir pruebas primero: TDD se trata de dejar que las pruebas lo conduzcan, escribirlas primero es solo un requisito previo necesario para eso.

No importa si realmente escribe el código o si está completamente desarrollado: está escribiendo (esqueletos de) código en su cabeza, luego escribe pruebas para ese código. Eso no es TDD.

Dejar ese hábito es difícil . Muy, muy duro. Parece ser especialmente difícil para programadores experimentados.

Keith Braithwaite ha creado un ejercicio que llama TDD como si lo quisieras decir . Consiste en un conjunto de reglas (basadas en las Tres Reglas de TDD del Tío Bob Martin , pero mucho más estrictas) que debe seguir estrictamente y que están diseñadas para guiarlo hacia la aplicación de TDD de manera más rigurosa. Funciona mejor con la programación de pares (para que tu pareja pueda asegurarse de que no estás rompiendo las reglas) y un instructor.

Las reglas son:

  1. Escriba exactamente una nueva prueba, la prueba más pequeña que pueda que parezca apuntar en la dirección de una solución
  2. Véalo fallar; las fallas de compilación cuentan como fallas
  3. Haga pasar la prueba desde (1) escribiendo el código de implementación mínimo que pueda en el método de prueba .
  4. Refactorizar para eliminar la duplicación y, de lo contrario, según sea necesario para mejorar el diseño. Sea estricto sobre el uso de estos movimientos:
    1. desea un nuevo método: espere hasta el tiempo de refactorización, luego ... cree nuevos métodos (que no sean de prueba) haciendo uno de estos, y de ninguna otra manera:
      • preferido: hacer Extraer método en el código de implementación creado según (3) para crear un nuevo método en la clase de prueba, o
      • si debe: mover el código de implementación según (3) a un método de implementación existente
    2. desea una nueva clase: espere hasta el momento de la refactorización, luego ... cree clases que no sean de prueba para proporcionar un destino para un Método Move y sin ningún otro motivo
    3. llenar clases de implementación con métodos haciendo Move Method, y de ninguna otra manera

Por lo general, esto conducirá a diseños muy diferentes que el "método pseudo-TDD" que se practica con frecuencia de "imaginar en su cabeza cuál debería ser el diseño, luego escribir pruebas para forzar ese diseño, implementar el diseño que ya había imaginado antes de escribir su pruebas ".

Cuando un grupo de personas implementa algo como un juego de tic tac toe usando pseudo-TDD, generalmente terminan con diseños muy similares que involucran algún tipo de Boardclase con una matriz de 3 × 3 de Integers. Y al menos una parte de los programadores habrá escrito esta clase sin pruebas porque "saben que la van a necesitar" o "necesitan algo contra lo que escribir sus pruebas". Sin embargo, cuando obligas a ese mismo grupo a aplicar TDD como si lo quisieras, a menudo terminarán con una amplia diversidad de diseños muy diferentes, a menudo sin emplear nada ni remotamente similar a a Board.

¿Hay alguna forma de identificar si la prueba que ha escrito es suficiente?

Cuando cubren todos los requisitos comerciales. Las pruebas son una codificación de los requisitos del sistema.

¿Es una buena práctica escribir una prueba para una funcionalidad muy simple que podría ser equivalente a 1 + 1 = 2 o es solo una exageración?

Una vez más, lo tienes al revés: no escribes pruebas de funcionalidad. Escribes funcionalidad para pruebas. Si la funcionalidad para aprobar la prueba resulta trivial, ¡genial! ¡Acaba de cumplir un requisito del sistema y ni siquiera tuvo que trabajar duro para cumplirlo!

¿Es bueno cambiar la funcionalidad y probar si los requisitos cambian?

No. Al revés. Si un requisito cambia, usted cambia la prueba que corresponde a ese requisito, observa cómo falla y luego cambia el código para que se apruebe. Las pruebas siempre son lo primero.

Es dificil hacer esto. Necesitas docenas, tal vez cientos de horas de práctica deliberada para construir algún tipo de "memoria muscular" para llegar a un punto, donde cuando se acerca la fecha límite y estás bajo presión, ni siquiera tienes que pensar en ello. , y hacerlo se convierte en la forma más rápida y natural de trabajar.


1
Una respuesta muy clara de hecho! Desde la perspectiva práctica, un marco de prueba flexible y potente es muy divertido al practicar TDD. Si bien es independiente de TDD, la capacidad de ejecutar pruebas automáticamente es invaluable para depurar una aplicación. Para comenzar con TDD, los programas no interactivos (estilo UNIX) son probablemente los más fáciles, porque se puede probar un caso de uso comparando el estado de salida y la salida del programa con lo que se espera. Un ejemplo concreto de este enfoque se puede encontrar en mi biblioteca de gasolina para OCaml.
Michael Le Barbier Grünewald

44
Dices "cuando obligas a ese mismo grupo a aplicar TDD como si lo quisieras decir, a menudo terminarán con una amplia diversidad de diseños muy diferentes, a menudo sin emplear nada ni remotamente similar a un tablero" como si fuera algo bueno . Para mí no está claro en absoluto que sea algo bueno, e incluso puede ser malo desde el punto de vista del mantenimiento, ya que parece que la implementación sería muy contraintuitiva para alguien nuevo. ¿Podría explicar por qué esta diversidad de implementación es algo bueno, o al menos no malo?
Jim Clay

3
+1 La respuesta es buena porque describe correctamente TDD. Sin embargo, también muestra por qué TDD es una metodología defectuosa: se necesita un pensamiento cuidadoso y un diseño explícito, especialmente cuando se enfrentan a problemas algorítmicos. Hacer TDD "a ciegas" (como lo prescribe TDD) al pretender no tener ningún conocimiento de dominio conduce a dificultades innecesarias y callejones sin salida. Vea la infame debacle del solucionador de Sudoku (versión corta: TDD no puede superar el conocimiento del dominio).
Andres F.

1
@AndresF .: En realidad, la publicación de blog a la que se vinculó parece hacer eco de las experiencias que Keith hizo al hacer TDD como si lo quisieras: al hacer "pseudo-TDD" para Tic-Tac-Toe, comienzan creando una Boardclase con un Matriz 3x3 de ints (o algo así). Mientras que, si los obliga a hacer TDDAIYMI, a menudo terminan creando un mini-DSL para capturar el conocimiento del dominio. Eso es anecdótico, por supuesto. Un estudio estadísticamente y científicamente sólido sería bueno, pero como suele ser el caso con estudios como este, son demasiado pequeños o demasiado caros.
Jörg W Mittag

@ JörgWMittag Corrígeme si te entendí mal, pero ¿estás diciendo que Ron Jeffries estaba haciendo "pseudo-TDD"? ¿No es esa una forma de la falacia del "no verdadero escocés"? (Estoy de acuerdo con usted sobre la necesidad de más estudios científicos; el blog al que me vinculé es solo una anécdota colorida sobre el espectacular fracaso de una instancia específica del uso de TDD. Desafortunadamente, parece que los evangelistas de TDD son demasiado ruidosos para el resto de nosotros para tener un análisis real de esta metodología y sus supuestos beneficios).
Andres F.

5

Describes tu enfoque de desarrollo como un proceso "de arriba abajo": comienzas desde un nivel de abstracción más alto y profundizas cada vez más en los detalles. TDD, al menos en la forma en que es popular, es una técnica "de abajo hacia arriba". Y para alguien que trabaja principalmente "de arriba hacia abajo", puede ser muy inusual trabajar "de abajo hacia arriba".

Entonces, ¿cómo puede aportar más "TDD" a su proceso de desarrollo? Primero, supongo que su proceso de desarrollo real no siempre es tan "de arriba abajo" como lo describió anteriormente. Después del paso 2, probablemente habrá identificado algunos componentes que son independientes de otros componentes. A veces decides implementar esos componentes primero. Los detalles de la API pública de esos componentes probablemente no sigan solo sus requisitos, los detalles también siguen sus decisiones de diseño. Este es el punto donde puede comenzar con TDD: imagine cómo va a usar el componente y cómo va a usar la API. Y cuando comienza a codificar un uso de API de este tipo en forma de prueba, acaba de comenzar con TDD.

En segundo lugar, puede hacer TDD incluso cuando va a codificar más "de arriba hacia abajo", comenzando con componentes que dependen primero de otros componentes no existentes. Lo que tiene que aprender es cómo "burlarse" de estas otras dependencias primero. Eso le permitirá crear y probar componentes de alto nivel antes de pasar a los componentes de nivel inferior. Un ejemplo muy detallado sobre cómo hacer TDD de arriba a abajo se puede encontrar en esta publicación de blog de Ralf Westphal .


3

¿Lo estoy haciendo bien? Si no es exactamente lo que tengo que cambiar

Lo estás haciendo bien.

¿Hay alguna forma de identificar si la prueba que ha escrito es suficiente?

Sí, use una herramienta de prueba / cobertura de código . Martin Fowler ofrece algunos buenos consejos sobre la cobertura de pruebas.

¿Es una buena práctica escribir una prueba para una funcionalidad muy simple que podría ser equivalente a 1 + 1 = 2 o es solo una exageración?

En general, cualquier función, método, componente, etc. que espera obtener algún resultado dadas algunas entradas es un buen candidato para una prueba unitaria. Sin embargo, como con la mayoría de las cosas en la vida (de ingeniería), debe considerar sus compensaciones: ¿se compensa el esfuerzo al escribir la prueba de la unidad, lo que resulta en una base de código más estable a largo plazo? En general, opte por escribir el código de prueba para la funcionalidad crucial / crítica primero. Más adelante, si encuentra que hay errores asociados con alguna parte no probada del código, agregue más pruebas.

¿Es bueno cambiar la funcionalidad y probar si los requisitos cambian?

Lo bueno de tener pruebas automatizadas es que verá de inmediato si un cambio rompe las afirmaciones anteriores. Si espera esto debido a los requisitos modificados, sí, está bien cambiar el código de prueba (de hecho, en TDD puro, primero cambiaría las pruebas de acuerdo con los requisitos, luego adoptaría el código hasta que cumpla con los nuevos requisitos).


La cobertura del código podría no ser una medida muy confiable. Hacer cumplir el porcentaje de cobertura generalmente resulta en muchas pruebas no necesarias (como pruebas para todas las verificaciones nulas de parámetros, etc., que son pruebas en aras de las pruebas que casi no agregan valor) y desperdician tiempo de desarrollo, mientras que es difícil probar el código los caminos pueden no ser probados en absoluto.
Paul

3

Escribir pruebas primero es un enfoque completamente diferente para escribir software. Las pruebas no son solo una herramienta de verificación adecuada de la funcionalidad del código (todas pasan) sino la fuerza que define el diseño. Si bien la cobertura de prueba puede ser una métrica útil, no debe ser el objetivo en sí mismo: el objetivo de TDD no es llegar a un buen% de cobertura de código, sino pensar en la capacidad de prueba de su código antes de escribirlo.

Si tiene problemas para escribir las pruebas primero, le recomiendo encarecidamente que haga una sesión de programación en pareja con alguien con experiencia en TDD, para que tenga una experiencia práctica de "la forma de pensar" sobre el enfoque completo.

Otra buena cosa que hacer es mirar videos en línea donde el software se está desarrollando usando TDD desde la primera línea. Uno bueno que solía presentarme a TDD fue Let's Play TDD de James Shore. Eche un vistazo, ilustrará cómo funciona el diseño emergente, qué preguntas debe hacerse mientras escribe las pruebas y cómo se crean, refactorizan e iteran nuevas clases y métodos.

¿Hay alguna forma de identificar si la prueba que ha escrito es suficiente?

Creo que esta es la pregunta incorrecta que hacer. Cuando hace TDD, elige hacer TDD y el diseño emergente como la forma de escribir software. Si alguna funcionalidad nueva que necesita agregar siempre comienza con una prueba, siempre estará allí.

¿Es una buena práctica escribir una prueba para una funcionalidad muy simple que podría ser equivalente a 1 + 1 = 2 o es solo una exageración?

Obviamente depende, usa tu juicio. Prefiero no escribir pruebas en las comprobaciones nulas de parámetros, si el método no es parte de una API pública, pero de lo contrario, ¿por qué no confirmaría que el método Add (a, b) devuelve a + b?

¿Es bueno cambiar la funcionalidad y probar si los requisitos cambian?

Nuevamente, cuando cambia o agrega una nueva funcionalidad a su código, comienza con una prueba, ya sea agregando una nueva prueba o cambiando una existente cuando cambian los requisitos.

Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.