Eu não entendo como o TDD me ajuda a ter um bom design se eu precisar de um design para começar a testá-lo

49

Estou tentando envolver minha cabeça em TDD, especificamente na parte de desenvolvimento. Eu olhei alguns livros, mas os que eu encontrei abordam principalmente a parte de teste - o Histórico do NUnit, por que testar é bom, Vermelho / Verde / Refatorar 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 eu 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 pesquisar um produto

Nesse ponto, muitos livros tiram um coelho mágico de dentro de um chapéu e simplesmente mergulham em "Testando o ProductService", mas não explicam como chegaram à conclusão de que há um ProductService em primeiro lugar. Essa é a parte "Desenvolvimento" em TDD que estou tentando entender.

Precisa haver um design existente, mas coisas fora dos serviços da entidade (ou seja: Há um Produto, portanto, deve haver um ProductService) não estão em lugar algum (por exemplo, o segundo requisito requer que eu tenha algum conceito de um usuário, mas onde eu colocaria a funcionalidade para lembrar? E é procurar um recurso do ProductService ou um SearchService separado? Como eu saberia o que devo escolher?)

De acordo com 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 pretende me fazer descobrir meu design em primeiro lugar?

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

    
por Michael Stum 29.05.2013 / 19:29
fonte

11 respostas

17

A idéia do TDD é começar com testes e trabalhar com isso. Assim, para tomar o seu exemplo de "Um catálogo precisa ter uma lista de produtos" poderia ser visto como tendo um teste de "Verificar os 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 ideia é juntar alguns pedaços que seriam algo como um ProductService que nascerá depois de fazer o 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 unitários fazem parte disso sim, mas você não está olhando para o quadro geral que é formado começando com os testes e, em seguida, escrevendo o código para que não haja pontos cegos neste ponto, pois ainda não existe nenhum código.

Tutorial de Desenvolvimento Orientado a Testes , onde os slides 20-22 são os principais. A ideia é saber o que a funcionalidade deve fazer como resultado, escrever um teste para ela e depois construir uma solução. A parte do design irá variar dependendo do que é 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 introduzir tardiamente em um projeto. Se você começar com os testes primeiro, isso pode ajudar e é provável que seja digno de nota. Se você tentar adicionar os testes mais tarde, 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 com os testes, você não está preso a um design inicialmente. Assim, a ideia é construir os testes e criar o código que passará esses testes como uma metodologia de desenvolvimento. Um Big Design Up Front pode causar problemas, pois isso dá a idéia de travar as coisas no lugar que faz com que o sistema seja construído para ser menos ágil no final.

Robert Harvey adicionou isso nos comentários que vale a pena declarar na resposta:

Unfortunately I think that this is a common misconception about TDD: you can't grow a software architecture by just writing unit tests and making them pass. Writing unit tests does influence the design, but it doesn't create the design. You have to do that.

    
por 29.05.2013 / 19:43
fonte
8

Por que vale a pena, o TDD me ajuda a chegar ao melhor design mais rapidamente do que não fazer o TDD. Eu provavelmente chegaria ao melhor design com ou sem ele. Mas o tempo que eu gastaria pensando e fazendo algumas tentativas no código é gasto escrevendo testes. E é menos tempo. Para mim. Não para todos. E, mesmo que demorasse o mesmo tempo, ele me deixaria com um conjunto de testes, para que a refatoração fosse mais segura, levando a um código ainda melhor no final.

Como isso acontece?

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

Em segundo lugar, ele me impede de escrever complexidade ciclomética demais em um método, enquanto eu estou pensando nisso. Cada caminho extra através de um método tenderá a duplicar o número de testes que preciso fazer. A pura preguiça determina que depois de adicionar muita lógica, e eu tenho que escrever 16 testes para adicionar uma condição, é hora de puxar um pouco para outro método / classe e testá-lo separadamente.

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

    
por 30.05.2013 / 00:24
fonte
6

I'm trying to wrap my head around TDD... To illustrate, imagine these 3 requirements:

  • A catalog needs to have a list of products
  • The catalog should remember which products a user viewed

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

  • Users should be able to search for a product

Como? Por nome? Por marca? O primeiro passo no desenvolvimento orientado a testes é 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"

>

At this points, many books pull a magic rabbit out of a hat and just dive into "Testing the ProductService", but they don't explain how they came to the conclusion that there is a ProductService in the first place.

Se estes são os únicos requisitos, eu certamente não iria pular para criar um ProductService. Eu poderia criar uma página web muito simples com uma lista de produtos estática. Isso funcionaria perfeitamente até você chegar aos 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 Product 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 evitar a duplicação de código.

Em resumo, o TDD orienta o código a ser escrito. O design acontece quando você faz escolhas de implementação e refaz 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 de uma classe ProductService. Você pode achar que a vida está perfeitamente bem com apenas uma classe de produto.

    
por 29.05.2013 / 23:26
fonte
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 explicam 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 todo o trabalho de teste.

Alguns exemplos em que me deparei com isso no passado:

  • Pegue um monte de empreiteiros e diga a eles que sua equipe está Agile e teste primeiro. Eles geralmente não têm outro hábito além de trabalhar para spec e eles não têm nenhuma preocupação com a qualidade do trabalho, desde que dura o suficiente para terminar o projeto.

  • Tente fazer um novo teste primeiro, gaste muito do seu tempo rasgando testes como você encontra várias abordagens e interfaces são uma porcaria.

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

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

Se você está fazendo TDD e está funcionando para você, é bom para você, mas há muitas coisas (trabalhos completos ou estágios de um projeto) lá fora, onde isso simplesmente não agrega valor.

O seu exemplo parece que você ainda não está em um design, então você precisa ter uma conversa sobre arquitetura ou protótipos. Você precisa passar um pouco disso primeiro na minha opinião.

    
por 29.05.2013 / 22:09
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 onde você começaria a usar o TDD, você precisa ter a grande figura do design já modelado de alguma forma e você precisa ter a grande figura da arquitetura já modelada de alguma forma. @ user414076 parafraseia Robert Martin por ter uma ideia de design em mente, mas não ser casado com ela. Exatamente. Conclusão - TDD não é a única atividade de projeto em andamento, é como os detalhes do projeto são aprofundados. 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 desenvolvido.

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

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

desenvolvimento orientado a testes Um Guia Prático - uma caminhada lenta e passo a passo através do desenvolvimento de um guia completo , embora pequeno, app.

    
por 05.06.2013 / 22:54
fonte
0

O TTD orienta a descoberta do projeto pela falha do teste, não pelo sucesso; portanto, você pode testar incógnitas e retestar iterativamente, já que as incógnitas estão expostas, levando a um conjunto completo de testes unitários - algo muito bom para manutenção contínua e muito difícil tentar retrofit após o código ser escrito / 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, dado qualquer qualquer formato de entrada . Obviamente, este teste falhará, então você escreve código para manipular os formatos conhecidos e reteste. Como os formatos desconhecidos são expostos através da coleta de requisitos, novos testes são escritos antes de o código ser escrito, eles também devem falhar. Então, um novo código é escrito para suportar os novos formatos e os testes all são executados novamente, reduzindo a chance de regressão.

Também é útil pensar em falha de 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).

    
por 29.05.2013 / 22:47
fonte
0

Na pergunta, é dito:

... many books pull a magic rabbit out of a hat and just dive into "Testing the ProductService", but they don't explain how they came to the conclusion that there is a ProductService in the first place.

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

    
por 30.05.2013 / 01:12
fonte
0

Uma funcionalidade pode ter muitos designs e o TDD não lhe dirá qual deles é o melhor. Mesmo que os testes ajudem você a criar mais códigos modulares, ele também pode levar você a criar módulos que atendam aos requisitos dos testes e não à realidade da produção. Então você tem que entender onde você está indo e como as coisas devem se encaixar em toda a imagem. Caso contrário, existem requisitos funcionais e não funcionais, não se esqueça do último.

No que diz respeito ao design, refiro-me aos livros de Robert C. Martin (Agile Development), mas também aos Patterns of Enterprise Application Architecture e ao Design de driver de domínio, de Martin Fowler. O último especialmente é muito sistemático na extração das Entidades e Relações fora dos requisitos.

Em seguida, quando tiver uma boa noção das opções disponíveis para você sobre como gerenciar essas entidades, você poderá fornecer a você a abordagem do TDD.

    
por 04.06.2013 / 07:50
fonte
0

Isn't TDD intended to make me discover my design in the first place?

Não.

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

To illustrate, imagine these 3 requirements:

  • A catalog needs to have a list of products
  • The catalog should remember which products a user viewed
  • Users should be able to search for a product

Estes não são requisitos, são definições de dados. Eu 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 as invariantes do seu sistema.

Um requisito seria algo como:

  • Um cliente pode solicitar uma determinada quantidade de produto, se houver produto suficiente em estoque.

Então, se este é 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 o 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);

    }

}

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

    
por 04.06.2013 / 12:21
fonte
0

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

    
por 06.06.2013 / 03:59
fonte
-3

TDD ajuda muito, no entanto, há uma parte importante no desenvolvimento de software. O desenvolvedor deve ouvir o código que está sendo escrito. A refatoração é a terceira parte no ciclo TDD. Este é o principal passo 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 a alta coesão e baixo acoplamento? E os nomes? Dê uma olhada no código que está surgindo dos testes e veja se há algo que precisa ser alterado, redesenhado. Questione o código e o código lhe dirá, como ele deseja ser projetado. Eu normalmente escrevo conjuntos de múltiplos testes, examino essa lista e crio o primeiro design simples, ele não precisa ser "final", geralmente não é, porque é alterado ao adicionar novos testes. É aí que vem o design.

    
por 04.06.2013 / 21:56
fonte

Tags