Não entendo como o TDD me ajuda a obter um bom design, se eu precisar de um design para começar a testá-lo.

50

Estou tentando entender o TDD, especificamente a parte do desenvolvimento. Eu olhei para alguns livros, mas os que encontrei abordam principalmente a parte dos testes - a História do NUnit, por que o teste é bom, Vermelho / Verde / Refator e como criar uma Calculadora de Cordas.

Coisas boas, mas isso é "apenas" Teste de Unidade, não TDD. Especificamente, não entendo como o TDD me ajuda a obter um bom design se precisar de um Design para começar a testá-lo.

Para ilustrar, imagine estes três requisitos:

  • Um catálogo precisa ter uma lista de produtos
  • O catálogo deve lembrar quais produtos um usuário visualizou
  • Os usuários devem poder procurar um produto

A essa altura, muitos livros tiram um coelho mágico do chapéu e apenas mergulham em "Testando o ProductService", mas eles não explicam como chegaram à conclusão de que existe um ProductService em primeiro lugar. Essa é a parte "Desenvolvimento" no TDD que estou tentando entender.

Precisa haver um design existente, mas coisas fora dos serviços da entidade (ou seja: Existe um Produto, portanto deve haver um ProductService) não podem ser encontradas (por exemplo, o segundo requisito exige que eu tenha algum conceito de Usuário, mas onde eu lembraria a funcionalidade? E a Pesquisa é um recurso do ProductService ou um SearchService separado? Como saberia qual devo escolher?)

De acordo com o SOLID , eu precisaria de um UserService, mas se eu projetar um sistema sem TDD, posso acabar com um monte de serviços de método único. O TDD não tem a intenção de me fazer descobrir o meu design em primeiro lugar?

Sou desenvolvedor .net, mas os recursos Java também funcionam. Sinto que não parece haver um aplicativo ou livro de amostra real que lide com um aplicativo de linha de negócios real. Alguém pode fornecer um exemplo claro que ilustra o processo de criação de um design usando TDD?

Michael Stum
fonte
2
TDD é apenas uma parte de toda a metodologia de desenvolvimento. É claro que você precisará empregar algum tipo de design (inicial ou melhor evolutivo) para reunir tudo.
Euphoric
3
@gnat: É uma pergunta sobre por que os livros TDD não tornam o processo de design mais claro.
Robert Harvey
4
@gnat: Foi sua edição, não minha. :) Veja minha alteração no título da pergunta e no corpo.
Robert Harvey
9
Se você leu o trabalho de Robert C. Martin ou talvez assistiu a um de seus vídeos, verá que ele geralmente tem um design em mente, mas não é casado com ele. Ele acredita que sua noção pré-concebida do design correto emergirá de seus testes, mas ele não o força. E, no final, às vezes esse design faz, e às vezes não. O que quero dizer aqui é que sua própria experiência anterior o guiará, mas os testes devem orientá-lo. Os testes devem poder desenvolver ou desmascarar seu design.
Anthony Pegram
3
Portanto, não é realmente sobre testes, é sobre design. Só que não está realmente ajudando você no design, mas sim na validação do design. Mas não é isso! @ # $ Ing testing?
Erik Reppen

Respostas:

17

A idéia do TDD é começar com testes e trabalhar com isso. Portanto, para dar o seu exemplo de "Um catálogo precisa ter uma lista de produtos", pode ser visto como um teste de "Verificar produtos no catálogo" e, portanto, este é o primeiro teste. Agora, o que contém um catálogo? O que contém um produto? Essas são as próximas peças e a idéia é reunir algumas partes que seriam algo como um ProductService que nascerá da aprovação do primeiro teste.

A idéia do TDD é começar com um teste e depois escrever o código que faz o teste passar como o primeiro ponto. Os testes de unidade fazem parte disso sim, mas você não está olhando para a imagem geral formada começando com testes e depois escrevendo o código para que não haja pontos cegos nesse momento, pois ainda não há código.


Tutorial de desenvolvimento orientado a testes, onde os slides 20 a 22 são os principais. A idéia é saber o que a funcionalidade deve fazer como resultado, escrever um teste para ela e criar uma solução. A parte do design variará conforme o necessário, pode ou não ser tão simples de fazer. Um ponto importante é usar o TDD desde o início, em vez de tentar se apresentar tarde no projeto. Se você começar com os testes primeiro, isso pode ajudar e provavelmente vale a pena notar em um sentido. Se você tentar adicionar os testes posteriormente, isso se tornará algo que pode ser adiado ou atrasado. Os slides posteriores também podem ser úteis.


Um dos principais benefícios do TDD é que, começando pelos testes, você não está preso a um design inicialmente. Assim, a idéia é construir os testes e criar o código que passará nesses testes como uma metodologia de desenvolvimento. Um grande projeto inicial pode causar problemas, pois isso dá a idéia de travar as coisas no lugar, o que torna o sistema sendo menos ágil no final.


Robert Harvey acrescentou isso nos comentários, que vale a pena afirmar na resposta:

Infelizmente, acho que esse é um equívoco comum sobre o TDD: você não pode desenvolver uma arquitetura de software apenas escrevendo testes de unidade e fazendo-os passar. Escrever testes de unidade influencia o design, mas não o cria . Você tem que fazer isso.

JB King
fonte
31
@ MichaelStum: Infelizmente, acho que esse é um equívoco comum sobre o TDD: você não pode desenvolver uma arquitetura de software apenas escrevendo testes de unidade e fazendo-os passar. Escrever testes de unidade influencia o design, mas não o cria . Você tem que fazer isso.
Robert Harvey
4
@RobertHarvey, JimmyHoffa: se eu pudesse votar seus comentários 100 vezes, eu o faria!
Doc Brown
9
@ Robert Harvey: Fico feliz que você tenha escrito sobre esse equívoco comum: ouço com frequência que basta sentar e escrever todos os tipos de testes de unidade e um design simplesmente "surge" espontaneamente. E se o seu design é ruim, é porque você não escreveu testes de unidade suficientes. Concordo com você que os testes são uma ferramenta para especificar e verificar os requisitos do seu design, mas "você precisa fazer" o design sozinho. Eu concordo totalmente.
Giorgio
4
@Giorgo, RobertHarvey: +1000 para RobertHarvey também. Infelizmente, esse equívoco é comum o suficiente para que alguns profissionais "especialistas" do TDD / Agile acreditem que seja verdade. Como por exemplo, eles fingem que você pode "evoluir" um solucionador de sudoku a partir do TDD, sem conhecimento ou análise de qualquer tipo . Gostaria de saber se Ron Jeffries alguma vez publicou um acompanhamento das limitações do TDD ou explicou por que de repente ele interrompeu seu experimento sem conclusões ou lições aprendidas.
Andrés F.
3
@ André F: Conheço a história do sudoku e acho muito interessante. Eu acho que alguns desenvolvedores cometem o erro de pensar que uma ferramenta (por exemplo, TDD ou SCRUM) pode substituir o conhecimento do domínio e seus próprios esforços, e espera que, aplicando mecanicamente um método específico, um bom software "magicamente surja". Geralmente, são pessoas que não gostam de gastar muito tempo em análise e design e preferem codificar algo diretamente. Para eles, seguir uma metodologia específica é um álibi para não fazer o design adequado. Mas isso é IMHO um mau uso do TDD.
Giorgio
8

Pelo que vale, o TDD me ajuda a obter o melhor design mais rapidamente do que não o TDD. Eu provavelmente chegaria ao melhor design com ou sem ele. Mas o tempo que eu teria gasto pensando e dando algumas facadas no código é gasto escrevendo testes. E é menos tempo. Para mim. Não para todos. E, mesmo que demorasse a mesma quantidade de tempo, isso me deixaria com um conjunto de testes, para que a refatoração fosse mais segura, levando a um código ainda melhor na linha.

Como isso acontece?

Primeiro, ele me incentiva a pensar em todas as classes como um serviço para algum código de cliente. Um código melhor vem do pensamento sobre como o código de chamada deseja usar a API, em vez de se preocupar com a aparência do código.

Segundo, me impede de escrever muita complexidade ciclomética em um método, enquanto estou pensando nisso. Cada caminho extra através de um método tende a dobrar o número de testes que preciso fazer. A preguiça pura determina que, depois de adicionar muita lógica, e eu precisar escrever 16 testes para adicionar uma condição, é hora de extrair parte dela para outro método / classe e testá-la separadamente.

É realmente assim tão simples. Não é uma ferramenta de design mágico.

pdr
fonte
6

Estou tentando entender o TDD ... Para ilustrar, imagine esses 3 requisitos:

  • Um catálogo precisa ter uma lista de produtos
  • O catálogo deve lembrar quais produtos um usuário visualizou

Esses requisitos devem ser reafirmados em termos humanos. Quem quer saber quais produtos o usuário visualizou anteriormente? O usuário? Um vendedor?

  • Os usuários devem poder procurar um produto

Quão? Pelo nome? Por marca? A primeira etapa no desenvolvimento orientado a teste é definir um teste, por exemplo:

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

>

A essa altura, muitos livros tiram um coelho mágico do chapéu e apenas mergulham em "Testando o ProductService", mas eles não explicam como chegaram à conclusão de que existe um ProductService em primeiro lugar.

Se esses são os únicos requisitos, eu certamente não saltaria para criar um ProductService. Eu poderia criar uma página da web muito simples com uma lista de produtos estática. Isso funcionaria perfeitamente até você atingir os requisitos para adicionar e excluir produtos. Nesse ponto, posso decidir que é mais simples usar um banco de dados relacional e um ORM e criar uma classe de produto mapeada para uma única tabela. Ainda não há ProductService. Classes como ProductService serão criadas quando e se forem necessárias. Pode haver várias solicitações da Web que precisam executar as mesmas consultas ou atualizações. Em seguida, a classe ProductService será criada para impedir a duplicação de código.

Em resumo, o TDD direciona o código a ser gravado. O design acontece quando você faz escolhas de implementação e depois refatora o código em classes para eliminar a duplicação e controlar as dependências. Ao adicionar código, você precisará criar novas classes para manter o código SOLID. Mas você não precisa decidir antecipadamente que precisará de uma classe Product e uma classe ProductService. Você pode achar que a vida é perfeita com apenas uma classe de produto.

Kevin Cline
fonte
OK, não ProductServiceentão. Mas como o TDD disse que você precisava de um banco de dados e um ORM?
Robert Harvey
4
@ Robert: não. É uma decisão de design, baseada no meu julgamento da maneira mais eficaz de atender aos requisitos. Mas a decisão pode mudar.
Kevin cline
11
Um bom design nunca será produzido como efeito colateral de algum processo arbitrário. Ter um sistema ou modelo para trabalhar e enquadrar as coisas é ótimo, mas o IMO de teste primeiro, IMO, atinge um conflito de interesses ao se vender também como algo que garantirá que as pessoas não sejam mordidas inesperadamente por efeitos colaterais de maus resultados. código que não deveria ter acontecido em primeiro lugar. O design requer reflexão, consciência e premeditação. Você não aprende quem remove os sintomas descobertos automaticamente da árvore. Você os aprende descobrindo como evitar os galhos mutantes do mal em primeiro lugar.
Erik Reppen
Eu acho que o teste 'adiciona um produto; reinicie o computador e reinicie o sistema; o produto adicionado ainda deve estar visível. ' mostra de onde vem a necessidade de algum tipo de banco de dados (mas ainda pode ser um arquivo simples ou XML).
yatima2975
3

Outros podem discordar, mas para mim muitas das metodologias mais recentes se baseiam na suposição de que o desenvolvedor fará a maior parte do que as metodologias mais antigas explicaram apenas por hábito ou orgulho pessoal, que o desenvolvedor geralmente está fazendo algo que é bastante óbvio para eles, e o trabalho é encapsulado em uma linguagem limpa ou nas partes mais limpas de uma linguagem um tanto bagunçada, para que você possa fazer todos os negócios de teste.

Alguns exemplos em que me deparei com isso no passado:

  • Pegue um grupo de contratados especializados e diga a eles que a equipe deles é Agile e Test First. Eles geralmente não têm outro hábito além de trabalhar com especificações e não se preocupam com a qualidade do trabalho, desde que ele dure o tempo suficiente para concluir o projeto.

  • Tente fazer algo novo primeiro, gaste muito do seu tempo testando testes, pois você acha várias abordagens e interfaces uma porcaria.

  • Codifique algo de baixo nível e receba um tapa por falta de cobertura ou escreva muitos testes que não valem muito valor, porque você não pode zombar dos comportamentos subjacentes aos quais está vinculado.

  • Qualquer situação em que você não tenha o suficiente da mecânica subjacente com antecedência para adicionar um recurso testável sem escrever primeiro um monte de bits não testáveis ​​subjacentes, como o subsistema de disco ou uma interface de comunicação no nível tcpip.

Se você está fazendo TDD e está funcionando para você, é bom para você, mas existem muitas coisas (trabalhos inteiros ou estágios de um projeto) por aí onde isso simplesmente não agrega valor.

Seu exemplo parece que você ainda não fez parte de um design; portanto, você precisa ter uma conversa sobre arquitetura ou está fazendo prototipagem. Você precisa passar por isso primeiro em minha opinião.

Conta
fonte
1

Estou convencido de que o TDD é uma abordagem muito valiosa para o design detalhado do sistema - ou seja, as APIs e o modelo de objeto. No entanto, para chegar ao ponto em um projeto em que você começaria a usar o TDD, é necessário que o quadro geral do design já tenha sido modelado de alguma maneira e o quadro geral da arquitetura já modelado de algum modo. @ user414076 parafraseia Robert Martin como tendo uma ideia de design em mente, mas não sendo casada com ela. Exatamente. Conclusão - TDD não é a única atividade de design em andamento, é como os detalhes do design são aprimorados. O TDD deve ser precedido por outras atividades de design e se encaixar em uma abordagem geral (como o Agile), que aborda como o design geral é criado e evoluído.

FYI - dois livros que recomendo sobre o tópico que dão exemplos tangíveis e realistas:

Crescimento de software orientado a objetos, guiado por testes - explica e fornece um exemplo completo do projeto. Este é um livro sobre design, não teste . O teste é usado como um meio de especificar o comportamento esperado durante as atividades de design.

desenvolvimento orientado a testes Um Guia Prático - uma caminhada lenta e passo a passo no desenvolvimento de um aplicativo completo, embora pequeno.

Chuck Krutsinger
fonte
0

O TTD impulsiona a descoberta do projeto por falha no teste, não por sucesso. Portanto, você pode testar as incógnitas e retestar iterativamente à medida que as incógnitas são expostas, o que leva a um conjunto completo de testes de unidade - uma coisa muito boa para manutenção contínua e uma coisa muito difícil de tentar. atualização após o código ser gravado / liberado.

Por exemplo, um requisito pode ser que a entrada possa estar em vários formatos diferentes, nem todos são conhecidos ainda. Usando o TDD, você primeiro escreveria um teste que verifica se a saída apropriada é fornecida, em qualquer formato de entrada. Obviamente, esse teste falhará, então você escreve um código para lidar com os formatos conhecidos e realiza um novo teste. Como os formatos desconhecidos são expostos através da coleta de requisitos, novos testes são gravados antes que o código seja gravado, e eles também devem falhar. Em seguida, um novo código é gravado para dar suporte aos novos formatos e todos os testes são executados novamente, reduzindo a chance de regressão.

Também é útil pensar na falha da unidade como código "inacabado" em vez de código "quebrado". O TDD permite unidades inacabadas (falhas esperadas), mas reduz a ocorrência de unidades quebradas (falhas inesperadas).

Daniel Pereira
fonte
11
Concordo que este é um fluxo de trabalho válido, mas não explica realmente como a arquitetura de alto nível pode emergir desse fluxo de trabalho.
Robert Harvey
11
Certo, uma arquitetura de alto nível, como o padrão MVC, não emergirá apenas do TDD. Mas, o que pode emergir do TDD é um código projetado para ser facilmente testável, o que é uma consideração de design em si.
Daniel Pereira
0

Na pergunta está afirmado:

... muitos livros tiram um coelho mágico do chapéu e apenas mergulham em "Testando o ProductService", mas eles não explicam como chegaram à conclusão de que existe um ProductService em primeiro lugar.

Eles chegaram a essa conclusão pensando em como iam testar este produto. "Que tipo de produto faz isso?" "Bem, nós poderíamos criar um serviço". "Ok, vamos escrever um teste para esse serviço"

Bryan Oakley
fonte
0

Uma funcionalidade pode ter muitos designs e o TDD não indica completamente qual é o melhor. Mesmo que os testes o ajudem a criar um código mais modular, ele também pode levar a criar módulos que atendam aos requisitos de testes e não à realidade da produção. Então você precisa entender para onde está indo e como as coisas devem se encaixar em todo o cenário. Em outras palavras, existem requisitos funcionais e não funcionais, não se esqueça do último.

Em relação ao design, refiro-me aos livros de Robert C. Martin (Desenvolvimento Ágil), mas também aos Padrões de Arquitetura de Aplicativos Corporativos e Design de Driver de Domínio de Martin Fowler. O último, especialmente, é muito sistemático ao extrair as Entidades e Relações dos requisitos.

Então, quando você tiver uma boa noção das opções disponíveis sobre como gerenciar essas entidades, poderá alimentar sua abordagem de TDD.

Ludovic
fonte
0

O TDD não tem a intenção de me fazer descobrir o meu design em primeiro lugar?

Não.

Como você pode testar algo que você não criou primeiro?

Para ilustrar, imagine estes três requisitos:

  • Um catálogo precisa ter uma lista de produtos
  • O catálogo deve lembrar quais produtos um usuário visualizou
  • Os usuários devem poder procurar um produto

Estes não são requisitos, são definições de dados. Não sei qual é o negócio do seu software, mas não é provável que os analistas falem dessa maneira.

Você precisa saber quais são os invariantes do seu sistema.

Um requisito seria algo como:

  • Um cliente pode solicitar um determinado produto em quantidade, se houver estoque suficiente em estoque.

Portanto, se esse é o único requisito, você pode ter uma classe como:

public class Product {

  private int quantity;

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

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

}

Em seguida, usando TDD, você escreveria um caso de teste antes de implementar o 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);

    }

}

Portanto, o segundo teste falhará, e você poderá implementar o método order () da maneira que desejar.

Guillaume
fonte
0

Você está certo de que o TDD resultará em uma boa implementação de um determinado design. Não ajudará no seu processo de design.

James Anderson
fonte
No entanto, fornece a rede de segurança para melhorar o design sem quebrar o código de funcionamento. Essa é a refatoração que a maioria das pessoas ignora.
Adrian Schneider
-3

O TDD ajuda muito, no entanto, há parte importante no desenvolvimento de software. O desenvolvedor deve ouvir o código que está sendo gravado. A refatoração é a 3ª parte do ciclo TDD. Este é o passo principal em que o desenvolvedor deve se concentrar e pensar antes de ir para o próximo teste vermelho. Existe alguma duplicação? Os princípios do SOLID são aplicados? E quanto à alta coesão e baixo acoplamento? E os nomes? Dê uma olhada no código que está emergindo dos testes e veja se há algo que precisa ser alterado, redesenhado. Pergunta o código e o código dirá a você como ele deseja que seja projetado. Geralmente escrevo conjuntos de vários testes, examino essa lista e crio o primeiro design simples, não precisa ser "final", geralmente não é, porque mudou ao adicionar novos testes. É aí que o design vem.

Adronius
fonte