2010-12-31 30 views
20

Como soy un novato de TDD, actualmente estoy desarrollando una pequeña aplicación de consola C# para practicar (porque la práctica hace la perfección, ¿no?). Empecé haciendo un simple bosquejo de cómo se podría organizar la aplicación (en lo que respecta a las clases) y comencé a desarrollar todas las clases de dominio que podía identificar, una por una (prueba primero, por supuesto).Usando TDD: "arriba hacia abajo" vs. "abajo hacia arriba"

Al final, las clases tienen que integrarse juntas para hacer que la aplicación se pueda ejecutar, es decir, colocar el código necesario en el método Principal que llama a la lógica necesaria. Sin embargo, no veo cómo puedo hacer este último paso de integración de una manera "prueba primero".

Supongo que no tendría estos problemas si hubiera usado un enfoque de "arriba hacia abajo". La pregunta es: ¿cómo podría hacer eso? ¿Debería haber comenzado probando el método Main()?

Si alguien me puede dar algunos consejos, será muy apreciado.

+0

¿Tiene algún caso de prueba de alto nivel, que si se aprueba confirmará que la aplicación está funcionando y que el problema se ha resuelto? Algo para demostrar que todo funciona (manteniendo la interfaz de usuario, por supuesto). (También soy un novato de TDD.) –

+0

No tengo ningún caso de prueba de alto nivel, y tal vez eso es lo que falta. La única pregunta es: ¿cómo sería una prueba de alto nivel? La aplicación solo produce texto en la consola. ¿Debo afirmar lo que se escribe allí? – Chris

+0

Cuando dices "integración", esto no significa que quieras probar que el cableado de los objetos sea correcto, pero que todas las clases juntas funcionen como se esperaba. Ahora, la mayoría de las personas recomiendan que debe tener algunos casos de prueba que le digan si ya terminó el trabajo. Lo que tienes en la función principal es probablemente una prueba, como señaló Marcus, una prueba positiva. Los métodos principales, como ve, son los últimos métodos que se escriben en una aplicación impulsada por prueba. Te sugiero que mires las pruebas de aceptación. Este libro podría serle interesante: http://www.growing-object-oriented-software.com/ –

Respuesta

29

"De arriba hacia abajo" es already used in computing to describe an analysis technique. Sugiero usar el término "fuera de adentro" en su lugar.

Outside-in es un término de BDD, en el cual reconocemos que a menudo hay múltiples interfaces de usuario para un sistema. Los usuarios pueden ser otros sistemas además de personas. Un enfoque BDD es similar a un enfoque TDD; podría ayudarte, así que lo describiré brevemente.

En BDD comenzamos con un escenario, generalmente un ejemplo simple de un usuario que usa el sistema. Las conversaciones sobre los escenarios nos ayudan a determinar qué debería hacer el sistema realmente do. Escribimos una interfaz de usuario, y podemos automatizar los escenarios contra esa interfaz de usuario si queremos.

Cuando escribimos la interfaz de usuario, la mantenemos lo más delgada posible. La interfaz de usuario usará otra clase, un controlador, un modelo de vista, etc., para lo cual podemos definir una API.

En esta etapa, la API será una clase vacía o una interfaz (de programa). Ahora podemos escribir ejemplos de cómo la interfaz de usuario podría usar este controlador y mostrar cómo el controlador entrega valor.

Los ejemplos también muestran el alcance de la responsabilidad del controlador y cómo delega sus responsabilidades a otras clases como repositorios, servicios, etc. Podemos expresar esa delegación usando simulaciones. Luego escribimos esa clase para hacer que los ejemplos (pruebas unitarias) funcionen. Escribimos solo ejemplos suficientes para hacer que nuestros escenarios de nivel de sistema pasen.

Encuentro que es común volver a trabajar en ejemplos falsos ya que las interfaces de los simulacros solo se adivinan al principio, y luego surgen más completamente a medida que se escribe la clase. Eso nos ayudará a definir la siguiente capa de interfaces o API, para lo cual describiremos más ejemplos, y así sucesivamente hasta que no se necesiten más simulacros y pase el primer escenario.

Como describimos más escenarios, creamos un comportamiento diferente en las clases y podemos refactorizar para eliminar la duplicación donde los diferentes escenarios y las interfaces de usuario requieren un comportamiento similar.

Al hacer esto de forma externa, obtenemos la mayor información posible sobre cuáles deben ser las API, y reelaboramos esas API lo más rápido que podemos. Esto se ajusta a los principios de Real Options (never commit early unless you know why). No creamos nada que no usemos, y las API en sí mismas están diseñadas para la facilidad de uso, más que para facilitar la escritura. El código tiende a escribirse en un estilo más natural utilizando el lenguaje de dominio más que el lenguaje de programación, lo que lo hace más legible. Como el código se lee aproximadamente 10 veces más de lo que está escrito, esto también ayuda a mantenerlo.

Por esa razón, usaría un enfoque de afuera adentro, en lugar de las conjeturas inteligentes de abajo hacia arriba. Mi experiencia es que resulta en un código más simple, más desacoplado, más legible y más fácil de mantener.

+0

Esto ayuda mucho, ¡gracias! Parece que debería usar una prueba de aceptación suprayacente para probar mi aplicación. – Chris

+1

He encontrado que la palabra "prueba" tiende a hacer que las personas piensen en fijar cosas, evitar que las cosas se rompan, asegurarse de que las cosas funcionen, etc. En cambio, trate de describir algunos ejemplos de cómo funcionan las cosas. Está ayudando a otras personas a comprender el valor de su código y cómo el comportamiento ofrece el valor para que puedan cambiarlo de manera segura. Es por eso que I (y otros BDDers) usan las palabras "escenario" y "ejemplo" en su lugar. Espero que tenga sentido. Solo se convierte en una prueba una vez que se escribe el código. – Lunivore

+2

Esta es una gran respuesta, muchas gracias. Muy informativo. +1 – weberc2

0

También puede probar la aplicación de su consola. Me pareció bastante duro, mira este ejemplo:

[TestMethod] 
public void ValidateConsoleOutput() 
{ 
    using (StringWriter sw = new StringWriter()) 
    { 
     Console.SetOut(sw); 
     ConsoleUser cu = new ConsoleUser(); 
     cu.DoWork(); 
     string expected = string.Format("Ploeh{0}", Environment.NewLine); 
     Assert.AreEqual<string>(expected, sw.ToString()); 
    } 
} 

Usted puede mirar en el full post here.

2

Si movió cosas fuera de main(), ¿no podría probar esa función?

Tiene sentido hacerlo, ya que es posible que desee ejecutar con diferentes cmd-args y desea probarlos.

+0

Sí, podría hacer eso, y eso es probablemente lo más fácil de hacer en este momento. Sin embargo, al principio, ¿no debería haber comenzado con una prueba de nivel superior para probar la aplicación? Se siente de esa manera, pero tal vez estoy haciendo demasiado con eso. – Chris

+0

Bueno, especificar entrada/salida a la consola en archivos es una forma, pero creo que funciona mejor para las pruebas de regresión, sin embargo, YMMV. El proyecto Mercurial DVCS hace exactamente esto en su suite, y funciona bastante bien para ellos, pero tienen comandos bastante buenos para inspeccionar los resultados de cualquier operación. – Macke

0

"de abajo hacia arriba" puede ahorrarle mucho trabajo porque ya tiene clases de bajo nivel (probadas) y puede usarlas en pruebas de nivel superior. en caso contrario, tendrías que escribir muchas maquetas (probablemente complicadas). también "de arriba hacia abajo" a menudo no funciona como se supone: piensas en algún buen modelo de alto nivel, lo pruebas y lo implementas, y luego te mueves para darte cuenta de que algunas de tus suposiciones eran incorrectas (es una situación bastante típica incluso para personas experimentadas) programadores). Por supuesto, los patrones ayudan aquí, pero tampoco es una bala de plata.


para tener una cobertura de prueba completa, tendrá que probar su principal en cualquier caso. No estoy seguro de que valga la pena el esfuerzo en todos los casos. Simplemente mueva toda la lógica de Main a la función separada (como propuso Marcus Lindblom), pruébela y deje que Main pase los argumentos de línea de comando a su función.


salida de la consola es una habilidad prueba más simple posible, y si se utiliza TDD puede ser utilizado simplemente para dar salida al resultado final w/o cualquier mensaje de diagnóstico y depuración. así que haga que su función de nivel superior devuelva resultados sólidos, pruébela y simplemente envíela en Main

0

Recomiendo "descender" (llamo a esa prueba de alto nivel). Escribí sobre eso:

http://www.hardcoded.net/articles/high-level-testing.htm

Así que sí, se debería probar la salida de la consola directamente. Claro, inicialmente es una molestia configurar una prueba para que sea fácil probar la salida de la consola directamente, pero si creas un código de ayuda apropiado, tus próximas pruebas de "alto nivel" serán mucho más fáciles de escribir. Al hacer eso, te empoderas para obtener un potencial ilimitado de factorización. Con "de abajo hacia arriba", su relación de clase inicial es muy rígida porque cambiar la relación significa cambiar las pruebas (mucho trabajo y peligroso).

0

Hubo una pieza interesante en MSDN magazine of December, que describe "cómo el ciclo BDD envuelve el ciclo tradicional de Desarrollo controlado por prueba (TDD) con pruebas de nivel de función que impulsan la implementación a nivel de unidad". Los detalles pueden ser demasiado específicos de la tecnología, pero las ideas y el esquema del proceso son relevantes para su pregunta.

0

Lo principal debe ser muy simple al final. No sé cómo se ve como en C#, pero en C++ debe ser algo como esto: objetos

#include "something" 

int main(int argc, char *argv[]) 
{ 
    return TaskClass::Run(argc, argv); 
} 

Pass ya creados a los constructores de las clases, pero en unidad de pruebas pasar objetos simulados.

Para obtener más información acerca de TDD, consulte these screencasts.Explican cómo hacer un desarrollo ágil, pero también habla de cómo hacer TDD, con ejemplos en C#.

Cuestiones relacionadas