Está criando os objetos que você acha que vai precisar ok em um primeiro teste no TDD

15

Sou relativamente novo no TDD e tenho problemas ao criar meu primeiro teste antes de qualquer código de implementação. Sem qualquer estrutura para o código de implementação, eu sou livre para escrever meu primeiro teste da forma que quiser, mas ele sempre parece estar manchado pela minha maneira Java / OO de pensar sobre o problema.

Por exemplo, no meu Github ConwaysGameOfLifeExemplo do primeiro teste que escrevi (rule1_zeroNeighbours), comecei criando um objeto GameOfLife que ainda não havia sido implementado; chamou um método set que não existia, um método step que não existia, um método get que não existia e, em seguida, usou uma declaração.

Os testes evoluíram à medida que escrevi mais testes e refatoramos, mas originalmente parecia algo assim:

@Test
public void rule1_zeroNeighbours()
{
    GameOfLife gameOfLife = new GameOfLife();
    gameOfLife.set(1, 1, true);
    gameOfLife.step();
    assertEquals(false, gameOfLife.get(1, 1));
}

Isso foi estranho, pois eu estava forçando o design da implementação com base em como eu havia decidido, nesta fase inicial, escrever este primeiro teste.

Da maneira que você entende TDD, está tudo bem? Parece que estou seguindo os princípios do TDD / XP, pois meus testes e implementação evoluíram ao longo do tempo com a refatoração. Portanto, se esse projeto inicial tivesse se mostrado inútil, estaria aberto a alterações, mas parece que estou forçando uma direção no solução iniciando dessa maneira.

De que outra forma as pessoas usam TDD? Eu poderia ter passado por mais iterações de refatoração começando com nenhum objeto GameOfLife, apenas primitivos e métodos estáticos, mas isso parece muito artificial.

Encaitar
fonte
5
O TDD não substitui um planejamento cuidadoso nem uma seleção cuidadosa de padrões de projeto. Dito isto, antes de escrever qualquer implementação para satisfazer as primeiras linhas de teste, é muito melhor do que depois de escrever dependências para reconhecer que seu plano é estúpido, que você escolheu o padrão errado ou mesmo apenas que é estranho ou confuso invocar uma classe da maneira que seu teste exige.
svidgen

Respostas:

9

Isso foi estranho, pois eu estava forçando o design da implementação com base em como eu havia decidido, nesta fase inicial, escrever este primeiro teste.

Eu acho que esse é o ponto principal da sua pergunta: se isso é desejável ou não, depende se você se inclina para a idéia do codeninja de que você deve projetar antecipadamente e depois usa o TDD para preencher a implementação, ou a ideia da durron de que os testes devem estar envolvidos. conduzindo o design e a implementação.

Penso qual destes você prefere (ou onde cai no meio) é algo que precisa descobrir por si mesmo como uma preferência. É útil entender os prós e os contras de cada abordagem. Provavelmente existem muitas, mas eu diria que as principais são:

Pro Upfront Design

  • Por mais que um processo TDD seja bom para impulsionar o design, ele não é perfeito. TDT sem um destino concreto em mente às vezes pode acabar em becos sem saída e, pelo menos, alguns desses becos sem saída poderiam ter sido evitados com um pouco de pensamento inicial sobre onde você quer chegar. Este artigo do blog cria esse argumento usando o exemplo do kata de algarismos romanos e tem uma implementação final bastante interessante para mostrar.

Design de condução de teste profissional

  • Ao criar sua implementação em torno de um cliente do seu código (seus testes), você obtém a adesão ao YAGNI praticamente de graça, desde que não comece a escrever casos de teste desnecessários. De maneira mais geral, você obtém uma API projetada para ser usada por um consumidor, que é o que você deseja.

  • A idéia de desenhar vários diagramas UML antes de escrever qualquer código e preencher as lacunas é boa, mas raramente realista. No Code Complete, de Steve McConnell, o design é famoso como um "problema perverso" - um problema que você não consegue entender completamente sem antes resolvê-lo pelo menos parcialmente. Junte isso ao fato de que o problema subjacente em si pode mudar com os requisitos variáveis, e esse modelo de design começa a parecer um pouco sem esperança. A condução de teste permite que você reduza um pedaço de trabalho de cada vez - no design, não apenas na implementação - e saiba que, pelo menos durante a vida útil de ficar vermelho para verde, essa tarefa ainda será atualizada e relevante.

Quanto ao seu exemplo em particular, como diz durron, se você adotou uma abordagem para conduzir o design escrevendo o teste mais simples, consumindo a interface mínima possível, provavelmente começaria com uma interface mais simples do que a do seu snippet de código .

Ben Aaronson
fonte
O link foi uma leitura muito boa, Ben. Obrigado por compartilhar isso.
precisa
1
@RubberDuck De nada! Na verdade, não concordo totalmente com isso, mas acho que faz um excelente trabalho argumentar esse ponto de vista.
Ben Aaronson
1
Também não tenho certeza, mas isso faz com que o caso seja bom. Eu acho que a resposta certa está em algum lugar no meio. Você precisa ter um plano, mas certamente se seus testes parecerem desajeitados, redesenhe. Enfim ... ++ bom show feijão velho.
precisa
17

Para escrever o teste em primeiro lugar, é necessário projetar a API que você implementará. Você já começou com o pé errado escrevendo seu teste para criar o objeto inteiro GameOfLife e usando isso para implementar seu teste.

Dos testes práticos de unidade com JUnit e Mockito :

No começo, você pode se sentir constrangido por escrever algo que nem está lá. Isso requer uma pequena alteração nos seus hábitos de codificação, mas depois de algum tempo você verá que é uma ótima oportunidade de design. Ao escrever os testes primeiro, você tem a chance de criar uma API que seja conveniente para o cliente usar. Seu teste é o primeiro cliente de uma API recém-nascida. É disso que realmente trata o TDD: o design de uma API.

Seu teste não faz muita tentativa de criar uma API. Você configurou um sistema com estado em que toda a funcionalidade está contida na GameOfLifeclasse externa .

Se eu escrevesse esse aplicativo, pensaria nas peças que quero construir. Por exemplo, eu posso fazer uma Cellaula, escrever testes para isso, antes de passar para o aplicativo maior. Certamente criaria uma classe para a estrutura de dados "infinita em todas as direções" necessária para implementar adequadamente o Conway e testá-lo. Uma vez feito tudo isso, eu pensaria em escrever a classe geral que possui um mainmétodo e assim por diante.

É fácil encobrir a etapa "escreva um teste que falha". Mas escrever o teste com falha que funciona da maneira que você deseja é o núcleo do TDD.

durron597
fonte
1
Considerando que uma célula seria apenas um invólucro para a boolean, esse design certamente seria pior para o desempenho. A menos que precise ser extensível no futuro para outros autômatos celulares com mais de dois estados?
user253751
2
@immibis Isso é brincadeira sobre detalhes. Você pode começar com uma classe que representa a coleção de células. Você também pode migrar / mesclar a classe de célula e seus testes com uma classe que representa uma coleção de células posteriormente, se o desempenho for um problema.
Eric
@immibis O número de vizinhos ativos pode ser armazenado por razões de desempenho. Número de carrapatos que a célula está viva, por motivos de coloração ..
Blorgbeard sai de 18/03/2015
A otimização prematura do @immibis é o mal ... Além disso, evitar a obsessão primitiva é a ótima maneira de escrever código de boa qualidade, não importa quantos estados ele suporte. Dê uma olhada em: jamesshore.com/Blog/PrimitiveObsession.html
Paul
0

Há uma escola diferente de pensamento sobre isso.

Alguns dizem: teste não compilando é erro - vá corrigir, escreva o menor código de produção disponível.

Alguns dizem: não há problema em escrever o teste primeiro, verificar se é uma droga (ou não) e depois criar classes / métodos ausentes

Com a primeira abordagem, você está realmente em um ciclo de refatoração de vermelho-verde. Com o segundo, você tem uma visão um pouco mais ampla do que deseja alcançar.

Cabe a você escolher a maneira como trabalha. IMHO ambas as abordagens são válidas.

timoras
fonte
0

Mesmo quando eu implemento algo de uma maneira "hack it juntos", ainda penso nas classes e etapas que estarão envolvidas em todo o programa. Então você já pensou nisso e escreveu esses pensamentos de design como um teste primeiro - isso é ótimo!

Agora, continue interagindo nas duas implementações para realizar esse teste inicial e, em seguida, adicione mais testes para melhorar e estender o design.

O que pode ajudá-lo é usar o Pepino ou similar para escrever seus testes.

gbjbaanb
fonte
0

Antes de começar a escrever seus testes, você deve pensar em como projetar seu sistema. Você deve gastar uma quantidade considerável de tempo durante a fase de design. Se você fez isso, não terá essa confusão sobre o TDD.

TDD é apenas um link de abordagem de desenvolvimento : TDD
1. Adicione um teste
2. Execute todos os testes e verifique se o novo falhar
3. Escreva um código
4. Execute testes
5. Refactor code
6. Repita

O TDD ajuda você a cobrir todos os recursos necessários que você planejou antes de começar a desenvolver seu software. link: Benefícios

codeninja.sj
fonte
0

Não gosto de testes de nível de sistema escritos em java ou c # por esse motivo. Observe o SpecFlow para c # ou um dos frameworks de teste baseados em Cucumber para java (talvez JBehave). Então seus testes podem se parecer mais com isso.

insira a descrição da imagem aqui

E você pode alterar o design do seu objeto sem precisar alterar todos os testes do sistema.

(Os testes de unidade "normais" são ótimos ao testar classes únicas.)

Quais são as diferenças entre as estruturas do BDD para Java?

Ian
fonte