No entiendo cómo TDD me ayuda a obtener un buen diseño si necesito un diseño para comenzar a probarlo


50

Estoy tratando de entender TDD, específicamente la parte de desarrollo. He visto algunos libros, pero los que encontré abordan principalmente la parte de prueba: la Historia de NUnit, por qué la prueba es buena, Rojo / Verde / Refactor y cómo crear una Calculadora de cadenas.

Cosas buenas, pero eso es "solo" Prueba de Unidad, no TDD. Específicamente, no entiendo cómo TDD me ayuda a obtener un buen diseño si necesito un diseño para comenzar a probarlo.

Para ilustrar, imagine estos 3 requisitos:

  • Un catálogo necesita tener una lista de productos.
  • El catálogo debe recordar qué productos ha visto un usuario
  • Los usuarios deberían poder buscar un producto

En este punto, muchos libros sacan un conejo mágico de un sombrero y simplemente se sumergen en "Probar el Servicio del Producto", pero no explican cómo llegaron a la conclusión de que hay un Servicio del Producto en primer lugar. Esa es la parte "Desarrollo" en TDD que estoy tratando de entender.

Es necesario que exista un diseño existente, pero no se encuentran elementos fuera de los servicios de la entidad (es decir: hay un producto, por lo que debe haber un servicio de producto) (por ejemplo, el segundo requisito requiere que tenga algún concepto de un Usuario, pero ¿dónde pondría la funcionalidad para recordar? ¿Y la búsqueda es una característica del ProductService o un SearchService separado? ¿Cómo sabría cuál debo elegir?)

Según SOLID , necesitaría un servicio de usuario, pero si diseño un sistema sin TDD, podría terminar con un montón de servicios de método único. ¿TDD no tiene la intención de hacerme descubrir mi diseño en primer lugar?

Soy desarrollador de .net, pero los recursos de Java también funcionarían. Siento que no parece haber una aplicación o libro de muestra real que trate con una línea real de aplicación comercial. ¿Alguien puede proporcionar un ejemplo claro que ilustre el proceso de creación de un diseño usando TDD?


2
TDD es solo una parte de toda la metodología de desarrollo. Por supuesto, necesitará emplear algún tipo de diseño (ya sea por adelantado o mejor evolutivo) para unir todo.
Eufórico

3
@gnat: Es una investigación sobre por qué los libros TDD no aclaran el proceso de diseño.
Robert Harvey

44
@gnat: Fue tu edición, no la mía. :) Vea mi cambio en el título de la pregunta y el cuerpo.
Robert Harvey

99
Si has leído el trabajo de Robert C. Martin o tal vez has visto uno de sus videos, verás que a menudo tiene un diseño en mente pero no está casado con él. Él cree que su noción preconcebida del diseño correcto surgirá de sus pruebas, pero no lo obliga a hacerlo. Y al final, a veces ese diseño sí, y otras no. Mi punto aquí es que su propia experiencia previa lo guiará, pero las pruebas deberían guiarlo. Las pruebas deberían poder desarrollar o desacreditar su diseño.
Anthony Pegram

3
Entonces, no se trata realmente de pruebas, se trata de diseño. Solo que no te ayuda realmente con el diseño, sino que te ayuda a validar el diseño. ¿Pero no es eso? @ # $ Ing prueba?
Erik Reppen

Respuestas:


17

La idea de TDD es comenzar con las pruebas y trabajar a partir de eso. Por lo tanto, tomar su ejemplo de "Un catálogo necesita tener una lista de productos" podría considerarse como una prueba de "Buscar productos en el catálogo" y, por lo tanto, esta es la primera prueba. Ahora, ¿qué contiene un catálogo? ¿Qué contiene un producto? Esas son las siguientes piezas y la idea es juntar algunos fragmentos que serían algo así como un ProductService que nacerá de pasar esa primera prueba.

La idea de TDD es comenzar con una prueba y luego escribir el código que hace pasar esa prueba como primer punto. Las pruebas unitarias son parte de este sí, pero no está mirando la imagen general que se forma al comenzar con las pruebas y luego escribir el código para que no haya puntos ciegos en este punto, ya que todavía no hay ningún código.


Test Driven Development Tutorial donde las diapositivas 20-22 son las claves. La idea es saber qué debería hacer la funcionalidad como resultado, escribir una prueba para ello y luego crear una solución. La parte de diseño variará ya que, dependiendo de lo que se requiera, puede o no ser tan simple de hacer. Un punto clave es usar TDD desde el principio en lugar de intentar introducirlo tarde en un proyecto. Si comienza con las pruebas primero, esto puede ayudar y es probable que valga la pena señalarlo en cierto sentido. Si intenta agregar las pruebas más tarde, se convierte en algo que puede posponerse o retrasarse. Las diapositivas posteriores también pueden ser útiles.


Un beneficio principal de TDD es que al comenzar con las pruebas, inicialmente no está bloqueado en un diseño. Por lo tanto, la idea es construir las pruebas y crear el código que pasará esas pruebas como metodología de desarrollo. Un gran diseño inicial puede causar problemas, ya que esto da la idea de bloquear las cosas en su lugar, lo que hace que el sistema que se está construyendo sea menos ágil al final.


Robert Harvey agregó esto en los comentarios que vale la pena indicar en la respuesta:

Desafortunadamente, creo que este es un error común sobre TDD: no se puede desarrollar una arquitectura de software simplemente escribiendo pruebas unitarias y haciéndolas pasar. Escribir pruebas unitarias influye en el diseño, pero no crea el diseño. Tienes que hacer eso.


31
@MichaelStum: Desafortunadamente, creo que este es un error común sobre TDD: no se puede desarrollar una arquitectura de software simplemente escribiendo pruebas unitarias y haciéndolas pasar. Escribir pruebas unitarias influye en el diseño, pero no crea el diseño. Tienes que hacer eso.
Robert Harvey

44
@RobertHarvey, JimmyHoffa: ¡si pudiera votar sus comentarios 100 veces, lo haría!
Doc Brown

99
@Robert Harvey: Me alegra que haya escrito sobre este error común: escucho con demasiada frecuencia que uno solo necesita sentarse y escribir todo tipo de pruebas unitarias y un diseño simplemente "surgirá" espontáneamente. Y si su diseño es malo, es porque no escribió suficientes pruebas unitarias. Estoy de acuerdo con usted en que las pruebas son una herramienta para especificar y verificar los requisitos de su diseño, pero "tiene que hacerlo" usted mismo. Estoy totalmente de acuerdo.
Giorgio

44
@Giorgo, RobertHarvey: +1000 a RobertHarvey de mi parte también. Desafortunadamente, ese concepto erróneo es lo suficientemente común como para que algunos practicantes de TDD / Agile "expertos" lo consideren cierto. Como, por ejemplo, pretenden que puede "desarrollar" un solucionador de sudoku fuera de TDD, sin conocimiento de dominio o análisis de ningún tipo . Me pregunto si Ron Jeffries alguna vez publicó un seguimiento de las limitaciones de TDD o explicó por qué de repente detuvo su experimento sin conclusiones ni lecciones aprendidas.
Andres F.

3
@Andres F: Conozco la historia sobre sudoku, y creo que es muy interesante. Creo que algunos desarrolladores cometen el error de pensar que una herramienta (por ejemplo, TDD o SCRUM) puede reemplazar el conocimiento del dominio y sus propios esfuerzos, y esperan que al aplicar mecánicamente un método en particular, un buen software "surgirá" mágicamente. A menudo son personas a las que no les gusta pasar demasiado tiempo en análisis y diseño y prefieren codificar algo directamente. Para ellos, seguir una metodología particular es una coartada para no hacer un diseño adecuado. Pero esto es en mi humilde opinión un mal uso de TDD.
Giorgio

8

Por lo que vale, TDD me ayuda a llegar al mejor diseño más rápido que no hacer TDD. Probablemente llegaría al mejor diseño con o sin él. Pero ese tiempo que habría pasado pensando y analizando el código se dedica a escribir pruebas. Y es menos tiempo. Para mi. No para todos. E, incluso si tomara la misma cantidad de tiempo, me dejaría con un conjunto de pruebas, de modo que la refactorización sería más segura, lo que llevaría a un código aún mejor en el futuro.

Como lo hace

Primero, me anima a pensar en cada clase como un servicio para algún código de cliente. Mejor código viene de pensar en cómo el código de llamada quiere usar la API en lugar de preocuparse por cómo debería verse el código en sí.

En segundo lugar, me impide escribir demasiada complejidad ciclométrica en un método, mientras lo estoy pensando. Cada ruta adicional a través de un método tenderá a duplicar la cantidad de pruebas que necesito hacer. La pura pereza dicta que después de haber agregado demasiada lógica, y tengo que escribir 16 pruebas para agregar una condición, es hora de sacar parte de ella en otro método / clase y probarla por separado.

Es realmente así de simple. No es una herramienta de diseño mágico.


6

Estoy tratando de entender TDD ... Para ilustrar, imagine estos 3 requisitos:

  • Un catálogo necesita tener una lista de productos.
  • El catálogo debe recordar qué productos ha visto un usuario

Estos requisitos deben reexpresarse en términos humanos. ¿Quién quiere saber qué productos ha visto el usuario anteriormente? ¿El usuario? ¿Un vendedor?

  • Los usuarios deberían poder buscar un producto

¿Cómo? ¿Por nombre? Por marca El primer paso en el desarrollo basado en pruebas es definir una prueba, por ejemplo:

browse to http://ourcompany.com
enter "cookie" in the product search box
page should show "chocolate-chip cookies" and "oatmeal cookies"

>

En este punto, muchos libros sacan un conejo mágico de un sombrero y simplemente se sumergen en "Probar el Servicio del Producto", pero no explican cómo llegaron a la conclusión de que hay un Servicio del Producto en primer lugar.

Si estos son los únicos requisitos, ciertamente no saltaría a crear un ProductService. Podría crear una página web muy simple con una lista de productos estática. Eso funcionaría perfectamente hasta llegar a los requisitos para agregar y eliminar productos. En ese punto, podría decidir que es más simple usar una base de datos relacional y un ORM, y crear una clase de Producto asignada a una sola tabla. Todavía no hay servicio de producto. Las clases como ProductService se crearán cuando sea necesario. Puede haber múltiples solicitudes web que necesiten realizar las mismas consultas o actualizaciones. Luego, se creará la clase ProductService para evitar la duplicación de código.

En resumen, TDD controla el código que se va a escribir. El diseño ocurre a medida que realiza elecciones de implementación y luego refactoriza el código en clases para eliminar la duplicación y controlar las dependencias. A medida que agregue código, deberá crear nuevas clases para mantener el código SOLIDO. Pero no necesita decidir con anticipación que necesitará una clase de producto y una clase de servicio de producto. Puede encontrar que la vida está perfectamente bien con solo una clase de Producto.


Ok, no ProductServiceentonces. Pero, ¿cómo le dijo TDD que necesitaba una base de datos y un ORM?
Robert Harvey

44
@Robert: no fue así. Es una decisión de diseño, basada en mi opinión sobre la forma más efectiva de cumplir con el requisito. Pero la decisión podría cambiar.
Kevin Cline

1
Un buen diseño nunca se producirá como un efecto secundario de algún proceso arbitrario. Tener un sistema o modelo con el que trabajar y enmarcar las cosas es genial, pero prueba primero, TDD, IMO, se enfrenta a un conflicto de intereses al venderse a sí mismo como algo que garantizará que las personas no sean mordidas inesperadamente por los efectos secundarios de los malos código que no debería haber sucedido en primer lugar. El diseño requiere reflexión, conciencia y previsión. No los aprende al eliminar los síntomas descubiertos automáticamente del árbol. Los aprendes descubriendo cómo evitar las ramas malvadas mutantes en primer lugar.
Erik Reppen

Creo que la prueba 'agrega un producto; reinicie la computadora y reinicie el sistema; el producto agregado aún debe estar visible ". muestra de dónde proviene la necesidad de algún tipo de base de datos (pero que aún podría ser un archivo plano o XML).
yatima2975 06/0613

3

Otros pueden estar en desacuerdo, pero para mí muchas de las metodologías más nuevas se basan en la suposición de que el desarrollador hará la mayor parte de lo que las metodologías más antiguas explican solo por hábito u orgullo personal, que el desarrollador generalmente está haciendo algo que es bastante obvio. para ellos, y el trabajo está encapsulado en un lenguaje limpio o en las partes más limpias de un lenguaje un tanto desordenado para que pueda hacer todo el trabajo de prueba.

Algunos ejemplos en los que me he encontrado con esto en el pasado:

  • Tome un grupo de contratistas de trabajos especiales y dígales que su equipo es Agile y Test First. A menudo no tienen otro hábito que no sea trabajar según las especificaciones y no les preocupa la calidad del trabajo mientras dure lo suficiente como para terminar el proyecto.

  • Intente hacer una prueba nueva primero, dedique gran parte de su tiempo a analizar las pruebas ya que encuentra que varios enfoques e interfaces son una mierda.

  • Codifique algo de bajo nivel y sea abofeteado por falta de cobertura, o escriba muchas pruebas que no sumen mucho valor porque no puede burlarse de los comportamientos subyacentes a los que está vinculado.

  • Cualquier situación en la que no tenga suficiente de la mecánica subyacente de antemano para agregar una función comprobable sin escribir primero un montón de bits no comprobables subyacentes, como el subsistema de disco o una interfaz de comunicación de nivel tcpip.

Si está haciendo TDD y está funcionando para usted, bien por usted, pero hay muchas cosas (trabajos completos o etapas de un proyecto) en el que esto simplemente no agrega valor.

Su ejemplo parece que aún no tiene un diseño, por lo que necesita tener una conversación de arquitectura o realizar un prototipo. Necesitas superar algo de eso primero en mi opinión.


1

Estoy convencido de que TDD es un enfoque muy valioso para el diseño detallado del sistema, es decir, las API y el modelo de objetos. Sin embargo, para llegar al punto en un proyecto en el que comenzaría a usar TDD, debe tener una imagen general del diseño ya modelada de alguna manera y debe tener una imagen general de la arquitectura ya modelada de alguna manera. @ user414076 parafrasea a Robert Martin por tener una idea de diseño en mente, pero no estar casada con ella. Exactamente. Conclusión: TDD no es la única actividad de diseño en curso, es cómo se desarrollan los detalles del diseño. El TDD debe estar precedido por otras actividades de diseño y adaptarse a un enfoque general (como Agile) que aborde cómo se crea y evoluciona el diseño general.

FYI: dos libros que recomiendo sobre el tema que dan ejemplos tangibles y realistas:

Creciente software orientado a objetos, guiado por pruebas : explica y ofrece un ejemplo completo de proyecto. Este es un libro sobre diseño, no sobre pruebas . Las pruebas se utilizan como un medio para especificar el comportamiento esperado durante las actividades de diseño.

desarrollo guiado por pruebas Una guía práctica : un recorrido lento y paso a paso para desarrollar una aplicación completa, aunque pequeña.


0

TTD impulsa el descubrimiento de diseño por falla de prueba, no por éxito, por lo tanto, puede probar incógnitas y volver a probar de forma iterativa a medida que las incógnitas están expuestas, lo que finalmente conduce a un arnés completo de pruebas unitarias, algo muy bueno para el mantenimiento continuo y algo muy difícil de intentar. actualizar después de que el código se escribe / libera.

Por ejemplo, un requisito puede ser que la entrada puede estar en varios formatos diferentes, aún no se conocen todos. Usando TDD, primero escribiría una prueba que verifique que se proporciona la salida adecuada dado cualquier formato de entrada. Obviamente, esta prueba fallará, por lo que debe escribir código para manejar los formatos conocidos y volver a realizar la prueba. Como los formatos desconocidos se exponen a través de la recopilación de requisitos, se escriben nuevas pruebas antes de que se escriba el código, estas también deberían fallar. Luego, se escribe un nuevo código para admitir los nuevos formatos y se vuelven a ejecutar todas las pruebas, lo que reduce la posibilidad de regresión.

También es útil pensar en la falla de la unidad como código "inacabado" en lugar de código "roto". TDD permite unidades sin terminar (fallas esperadas), pero reduce la aparición de unidades rotas (fallas inesperadas).


1
Estoy de acuerdo en que este es un flujo de trabajo válido, pero en realidad no explica cómo puede surgir una arquitectura de alto nivel de dicho flujo de trabajo.
Robert Harvey

1
Correcto, una arquitectura de alto nivel como el patrón MVC no va a surgir solo de TDD. Pero, lo que puede surgir de TDD es un código diseñado para ser fácilmente comprobable, lo cual es una consideración de diseño en sí mismo.
Daniel Pereira

0

En la pregunta se afirma:

... muchos libros sacan un conejo mágico de un sombrero y simplemente se sumergen en "Probar el Servicio del Producto", pero no explican cómo llegaron a la conclusión de que hay un Servicio del Producto en primer lugar.

Llegaron a esa conclusión al pensar en cómo iban a probar este producto. "¿Qué tipo de producto hace esto?" "Bueno, podríamos crear un servicio". "Ok, vamos a escribir una prueba para ese servicio"


0

Una funcionalidad puede tener muchos diseños y TDD no le dirá completamente cuál es el mejor. Incluso, si las pruebas lo ayudan a construir más código modular, también puede llevarlo a crear módulos que se adapten a los requisitos de las pruebas y no a la realidad de producción. Por lo tanto, debe comprender a dónde va y cómo deben encajar las cosas en la imagen completa. Dicho de otro modo, hay requisitos funcionales y no funcionales, no olvide el último.

Con respecto al diseño, me refiero a los libros de Robert C. Martin (Desarrollo ágil) pero también a los Patrones de arquitectura de aplicaciones empresariales y Diseño de controladores de dominio de Martin Fowler. El último especialmente es muy sistemático en la extracción de las Entidades y Relaciones fuera de los requisitos.

Luego, cuando tenga una buena idea de las opciones disponibles para usted sobre cómo administrar esas entidades, puede alimentar su enfoque TDD.


0

¿TDD no tiene la intención de hacerme descubrir mi diseño en primer lugar?

No.

¿Cómo puedes probar algo que no has diseñado primero?

Para ilustrar, imagine estos 3 requisitos:

  • Un catálogo necesita tener una lista de productos.
  • El catálogo debe recordar qué productos ha visto un usuario
  • Los usuarios deberían poder buscar un producto

Estos no son requisitos, son definiciones de datos. No sé cuál es el negocio de su software, pero no es probable que los analistas hablen de esa manera.

Necesita saber cuáles son los invariantes de su sistema.

Un requisito sería algo como:

  • Un cliente puede pedir una determinada cantidad de producto, si hay suficiente de este producto en stock.

Entonces, si este es el único requisito, puede tener una clase como:

public class Product {

  private int quantity;

  public Product(int initialQuantity) {
    this.quantity = initialQuantity;
  }

  public void order(int quantity) {
    // To be implemented.
  }

}

Luego, usando TDD, escribiría un caso de prueba antes de implementar el método order ().

public void ProductTest() {

    public void testCorrectOrder() {

        Product p = new Product(10);
        p.order(3);
        p.order(4);

    }

    @Expect(ProductOutOfStockException)
    public void testIncorrectOrder() {

        Product p = new Product(10);
        p.order(7);
        p.order(4);

    }

}

Entonces, la segunda prueba fallará, luego puede implementar el método order () de la manera que desee.


0

Estás en lo cierto, TDD dará como resultado una buena implementación de un diseño dado. No ayudará a su proceso de diseño.


Sin embargo, le brinda la red de seguridad para mejorar el diseño sin romper el código de trabajo. Esta es la refactorización que la mayoría de las personas omiten.
Adrian Schneider

-3

TDD ayuda mucho, sin embargo, hay una parte importante en el desarrollo de software. El desarrollador debe escuchar el código que se está escribiendo. La refactorización es la tercera parte del ciclo TDD. Este es el paso principal donde el desarrollador debe enfocarse y pensar antes de pasar a la siguiente prueba roja. ¿Hay alguna duplicación? ¿Se aplican los principios SÓLIDOS? ¿Qué pasa con la alta cohesión y el bajo acoplamiento? ¿Qué hay de los nombres? Eche un vistazo más de cerca al código que está surgiendo de las pruebas y vea si hay algo que necesita ser cambiado, rediseñado. Pregunta el código y el código te dirá, cómo quiere que se diseñe. Por lo general, escribo conjuntos de pruebas múltiples, examino esa lista y creo un primer diseño simple, no es necesario que sea "final", por lo general no lo es, porque cambia al agregar nuevas pruebas. Ahí es donde viene el diseño.

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.