¿Cómo puedo comenzar a usar TDD para codificar alguna funcionalidad simple?

9

Básicamente tengo la esencia de TDD. Estoy convencido de que es útil y tengo un comando razonable del marco MSTEST. Sin embargo, hasta la fecha no he podido graduarme para usarlo como método de desarrollo primario. Principalmente, lo uso como sustituto para escribir aplicaciones de consola como controladores de prueba (mi enfoque tradicional).

Lo más útil para mí es la forma en que absorbe el papel de las pruebas de regresión.

Todavía no he construido nada que aísle específicamente varios comportamientos comprobables, que es otra gran parte de la imagen que conozco.

Entonces, esta pregunta es para pedir punteros sobre cuáles son las primeras pruebas que podría escribir para la siguiente tarea de desarrollo: Quiero producir código que encapsule la ejecución de tareas a la manera del productor / consumidor.

Me detuve y decidí escribir esta pregunta después de escribir este código (preguntándome si realmente podría usar TDD de verdad esta vez)

Código:

interface ITask
{
    Guid TaskId { get; }
    bool IsComplete { get; }
    bool IsFailed { get; }
    bool IsRunning { get; }
}

interface ITaskContainer
{
    Guid AddTask(ICommand action);
}

interface ICommand
{
    string CommandName { get; }
    Dictionary<string, object> Parameters { get; }
    void Execute();
}
Aaron Anodide
fuente
¡Deberías haber escrito las pruebas primero y LUEGO las interfaces! La idea es que TDD es para su API.
1
¿Cómo se escriben pruebas para interfaces que aún no existen? Ni siquiera compilarán.
Robert Harvey
55
Esa es tu primera prueba reprobatoria.
cori

Respuestas:

10

Comenzando con este concepto:
1) Comience con el comportamiento que desea. Escribe una prueba para ello. Ver prueba fallida.
2) Escriba suficiente código para pasar la prueba. Ver todas las pruebas aprobadas.
3) Busque código redundante / descuidado -> refactor. Ver las pruebas aún pasan. Goto 1

Entonces, en el n. ° 1, digamos que desea crear un nuevo comando (me estoy estirando a cómo funcionaría el comando, así que tengan paciencia conmigo). (Además, seré un poco pragmático en lugar de extremo TDD)

El nuevo comando se llama MakeMyLunch, por lo que primero debe crear una prueba para crear una instancia y obtener el nombre del comando:

@Test
public void instantiateMakeMyLunch() {
   ICommand command = new MakeMyLunchCommand();
   assertEquals("makeMyLunch",command.getCommandName());
}

Esto falla, lo que le obliga a crear la nueva clase de comando y hacer que devuelva su nombre (el purista diría que se trata de dos rondas de TDD, no 1). Entonces crea la clase y le pide que implemente la interfaz ICommand, incluido el retorno del nombre del comando. La ejecución de todas las pruebas ahora muestra todos los pases, por lo que debe buscar oportunidades de refactorización. Probablemente ninguno.

Entonces, a continuación, desea que se implemente ejecutar. Entonces tienes que preguntar: ¿cómo sé que "MakeMyLunch" con éxito "hizo mi almuerzo". ¿Qué cambios en el sistema debido a esta operación? ¿Puedo hacer una prueba para esto?

Supongamos que es fácil probar para:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   ICommand command = new MakeMyLunchCommand();
   command.execute();
   assertTrue( Lunch.isReady() );
}

Otras veces, esto es más difícil, y lo que realmente quieres hacer es probar las responsabilidades del sujeto bajo prueba (MakeMyLunchCommand). Quizás la responsabilidad de MakeMyLunchCommand es interactuar con Fridge and Microwave. Entonces, para probarlo, puede usar una nevera simulada y una microonda simulada. [dos marcos simulados de muestra son Mockito y nMock o mira aquí .]

En cuyo caso, haría algo como el siguiente pseudocódigo:

@Test
public void checkThatMakeMyLunchIsSuccessful() {
   Fridge mockFridge = mock(Fridge);
   Microwave mockMicrowave = mock(Microwave);
   ICommand command = new MakeMyLunchCommand( mockFridge, mockMicrowave );
   command.execute();
   mockFramework.assertCalled( mockFridge.removeFood );
   mockFramework.assertCalled( microwave.turnon );
}

El purista dice que pruebe la responsabilidad de su clase: sus interacciones con otras clases (¿el comando abrió el refrigerador y encendió el microondas?).

El pragmático dice prueba para un grupo de clases y prueba para el resultado (¿está listo su almuerzo?).

Encuentre el equilibrio adecuado que funcione para su sistema.

(Nota: considere que quizás llegó a la estructura de su interfaz demasiado pronto. Quizás pueda dejar que esto evolucione a medida que escribe sus pruebas e implementaciones de la unidad, y en el paso # 3 "nota" la oportunidad común de interfaz).

jayraynet
fuente
Si no hubiera escrito previamente mi interfaz, ¿qué pregunta habría llevado a la creación del método Execute ()? Algunos de mis intentos iniciales de TDD se estancaron cuando no tenía un "paso" para estimular la funcionalidad adicional. la sensación es un problema latente de pollo / huevo que debe ser
evitado
1
¡Buena pregunta! Si el único comando que realizó fue "MakeMyLunchCommand", el método puede haber comenzado con ".makeMyLunch ()". Lo que hubiera estado bien. Luego haces otro comando ("NapCommand.takeNap ()"). Todavía no hay problema con los diferentes métodos. Luego, comienza a usarlo en su ecosistema, que es probablemente donde se ve obligado a generalizar en la interfaz ICommand. En general, a menudo demoras la generalización hasta el último momento responsable, porque YAGNI [ en.wikipedia.org/wiki/You_ain't_gonna_need_it ] =) Otras veces, sabes que vas a llegar allí, así que comienzas con él.
jayraynet
(Además de la asunción aquí es que usted está utilizando un IDE moderno que hace que la refactorización cosas como nombres de los métodos triviales)
jayraynet
1
gracias de nuevo por el consejo, es un hito para mí finalmente ver todas las piezas y cómo encajan, y sí, el refactor rápido está en mi caja de herramientas
Aaron Anodide