Como posso começar a usar o TDD para codificar algumas funcionalidades simples?

9

Eu basicamente tenho a essência do TDD. Eu sou vendido que é útil e eu tenho um comando razoável da estrutura MSTEST. No entanto, até o momento não consegui me dedicar a usá-lo como método primário de desenvolvimento. Principalmente, eu o uso como substituto para escrever aplicativos de console como drivers de teste (minha abordagem tradicional).

A coisa mais útil para mim é a maneira como absorve o papel do teste de regressão.

Ainda não construí nada que isole especificamente vários comportamentos testáveis, o que é outra grande parte da imagem que conheço.

Portanto, essa pergunta é para pedir ponteiros sobre o (s) primeiro (s) teste (s) que eu poderia escrever para a seguinte tarefa de desenvolvimento: Eu quero produzir código que encapsule a execução da tarefa à moda de produtor / consumidor.

Parei e decidi escrever essa pergunta depois de escrever esse código (pensando se seria possível usar o TDD de verdade neste momento)

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();
}
Anodeto de Aaron
fonte
Você deveria ter escrito os testes primeiro e depois as interfaces! A ideia é que o TDD seja para a sua API.
11
Como se escreve testes para interfaces que ainda não existem? Eles nem compilarão.
21412 Robert Harvey
5
Esse é o seu primeiro teste que falhou.
Cori

Respostas:

10

Começando com este conceito:
1) Comece com o comportamento que você deseja. Faça um teste para isso. Veja falha no teste.
2) Escreva código suficiente para passar no teste. Veja todos os testes aprovados.
3) Procure por código redundante / desleixado -> refatorar. Veja os testes ainda passam. Goto 1

Então, no 1, digamos que você deseja criar um novo comando (estou estendendo a maneira como o comando funcionaria, então tenha paciência comigo). (Além disso, vou ser um pouco pragmático, em vez de extremo TDD)

O novo comando é chamado MakeMyLunch, então você primeiro cria um teste para instancia-lo e obtém o nome do comando:

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

Isso falha, forçando você a criar a nova classe de comando e a retornar seu nome (o purista diria que são duas rodadas de TDD, não 1). Então você cria a classe e ela implementa a interface ICommand, incluindo o retorno do nome do comando. A execução de todos os testes agora mostra todas as aprovações, portanto, você procura oportunidades de refatoração. Provavelmente nenhum.

Então, em seguida, você deseja que ele execute execute. Então você tem que perguntar: como sei que "MakeMyLunch" com sucesso "preparou meu almoço". O que muda no sistema devido a esta operação? Posso testar isso?

Suponha que seja fácil testar:

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

Outras vezes, isso é mais difícil, e o que você realmente deseja fazer é testar as responsabilidades do sujeito em teste (MakeMyLunchCommand). Talvez a responsabilidade do MakeMyLunchCommand seja interagir com o Fridge and Microwave. Então, para testá-lo, você pode usar um refrigerador e um micro-ondas simulados. [dois exemplos de estruturas simuladas são Mockito e nMock ou veja aqui .]

Nesse caso, você faria algo como o seguinte pseudo-có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 );
}

O purista diz que teste a responsabilidade de sua classe - suas interações com outras classes (o comando abriu a geladeira e ligou o microondas?).

O pragmatista diz teste para um grupo de classes e teste para o resultado (o seu almoço está pronto?).

Encontre o equilíbrio certo que funciona para o seu sistema.

(Nota: considere que talvez você tenha chegado à sua estrutura de interface muito cedo. Talvez você possa deixar isso evoluir à medida que você escreve seus testes de unidade e implementações, e na etapa 3 você "percebe" a oportunidade de interface comum).

jayraynet
fonte
se eu não tivesse pré-escrito minha interface, que pergunta levaria à criação do método Execute () - algumas das minhas tentativas iniciais de TDD foram interrompidas quando não havia uma "etapa" para estimular a funcionalidade adicional - recebo a sensação há um problema latente galinha / ovo que tem de ser contornado
Aaron Anodide
11
Boa pergunta! Se o único comando que você fez foi "MakeMyLunchCommand", o método pode ter começado com ".makeMyLunch ()". O que teria sido bom. Então você faz outro comando ("NapCommand.takeNap ()"). Ainda não há problema com métodos diferentes. Então você começa a usá-lo em seu ecossistema, o que provavelmente é o local onde você é forçado a generalizar na interface ICommand. Em geral, você geralmente adia a generalização até o último momento responsável, porque YAGNI [ pt.wikipedia.org/wiki/You_ain't_gonna_need_it ] =) Outras vezes, você sabe que vai chegar lá, para começar.
Jayraynet
(Também de suposição aqui é que você está usando um IDE moderno que faz refatoração coisas como nomes de métodos trivial)
jayraynet
11
Obrigado novamente para o conselho, é uma espécie de um marco para mim para finalmente ver todas as peças e como eles se encaixam - e sim, refactor rápida está em minha caixa de ferramentas
Aaron Anodide