Quando você escreve o código “real” no TDD?

146

Todos os exemplos que li e vi em vídeos de treinamento têm exemplos simplistas. Mas o que eu não vejo se eu faço o código "real" depois que eu fico verde. Esta é a parte "Refatorar"?

Se eu tiver um objeto bastante complexo com um método complexo, escrevo meu teste e o mínimo para fazê-lo passar (depois que ele falhar primeiro, Red). Quando eu volto e escrevo o código real? E quanto código real escrevo antes de voltar a testar? Eu estou supondo que o último é mais intuição.

Editar: Obrigado a todos que responderam. Todas as suas respostas me ajudaram imensamente. Parece haver idéias diferentes sobre o que eu estava perguntando ou confuso, e talvez haja, mas o que eu estava perguntando era, digamos que eu tivesse um pedido de construção de uma escola.

No meu design, tenho uma arquitetura com a qual quero começar, User Stories, e assim por diante. A partir daqui, pego essas Estórias de Usuário e crio um teste para testar a História do Usuário. O usuário diz: Temos pessoas que se matriculam para a escola e pagam taxas de inscrição. Então, penso em uma maneira de fazer isso falhar. Ao fazer isso, desenvolvo uma classe de teste para a classe X (talvez Student), que falhará. Eu então criei a classe "Student". Talvez "escola" eu não sei.

Mas, em qualquer caso, o Design TD está me forçando a pensar na história. Se eu posso fazer um teste falhar, sei por que ele falha, mas isso pressupõe que eu possa fazê-lo passar. É sobre o design.

Eu comparo isso a pensar em Recursão. Recursão não é um conceito difícil. Pode ser mais difícil controlar isso na sua cabeça, mas na realidade, a parte mais difícil é saber, quando a recursão "quebra", quando parar (minha opinião, é claro). Então eu tenho que pensar sobre o que para a recursão primeiro. É apenas uma analogia imperfeita e presume que cada iteração recursiva é uma "passagem". Mais uma vez, apenas uma opinião.

Na implementação, a escola é mais difícil de ver. Registros numéricos e bancários são "fáceis", no sentido de que você pode usar aritmética simples. Eu posso ver a + b e retornar 0, etc. No caso de um sistema de pessoas, eu tenho que pensar mais em como implementar isso. Eu tenho o conceito de falha, aprovação, refatoração (principalmente por causa do estudo e dessa questão).

O que eu não sei é baseado na falta de experiência, na minha opinião. Eu não sei como deixar de inscrever um novo aluno. Eu não sei como falhar alguém digitando um sobrenome e sendo salvo em um banco de dados. Eu sei como fazer um + 1 para matemática simples, mas com entidades como uma pessoa, eu não sei se estou apenas testando para ver se eu recebo um ID único do banco de dados ou outra coisa quando alguém insere um nome em um banco de dados ou ambos ou nenhum deles.

Ou talvez isso mostre que ainda estou confuso.

    
por johnny 24.07.2017 / 23:55
fonte

11 respostas

241

If I have a fairly complex object with a complex method, and I write my test and the bare minimum to make it pass (after it first fails, Red). When do I go back and write the real code? And how much real code do I write before I retest? I'm guessing that last one is more intuition.

Você não "volta" e escreve "código real". É tudo código real. O que você faz é voltar e adicionar outro teste que force você a alterar seu código para fazer o novo teste passar.

Quanto de quanto você escreve código antes de testar novamente? Nenhum. Você escreve o código zero sem um teste de falha que força você a escrever mais código.

Observe o padrão?

Vamos analisar outro exemplo simples na esperança de que isso ajude.

Assert.Equal("1", FizzBuzz(1));

Fácil peazy.

public String FizzBuzz(int n) {
    return 1.ToString();
}

Não é o que você chamaria de código real, certo? Vamos adicionar um teste que force uma mudança.

Assert.Equal("2", FizzBuzz(2));

Poderíamos fazer algo bobo como if n == 1 , mas vamos pular para a solução sensata.

public String FizzBuzz(int n) {
    return n.ToString();
}

Legal. Isso funcionará para todos os números não-FizzBuzz. Qual é a próxima entrada que irá forçar o código de produção a mudar?

Assert.Equal("Fizz", FizzBuzz(3));

public String FizzBuzz(int n) {
    if (n == 3)
        return "Fizz";
    return n.ToString();
}

E novamente. Escreva um teste que não passará ainda.

Assert.Equal("Fizz", FizzBuzz(6));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    return n.ToString();
}

E agora cobrimos todos os múltiplos de três (que também não são múltiplos de cinco, vamos anotar e voltar).

Ainda não escrevemos um teste para "Buzz", então vamos escrever isso.

Assert.Equal("Buzz", FizzBuzz(5));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n == 5)
        return "Buzz"
    return n.ToString();
}

E novamente, sabemos que há outro caso que precisamos lidar.

Assert.Equal("Buzz", FizzBuzz(10));

public String FizzBuzz(int n) {
    if (n % 3 == 0)
        return "Fizz";
    if (n % 5 == 0)
        return "Buzz"
    return n.ToString();
}

E agora podemos lidar com todos os múltiplos de 5 que também não são múltiplos de 3.

Até este ponto, ignoramos a etapa de refatoração, mas vejo alguma duplicação. Vamos limpar isso agora.

private bool isDivisibleBy(int divisor, int input) {
    return (input % divisor == 0);
}

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

Legal. Agora nós removemos a duplicação e criamos uma função bem nomeada. Qual é o próximo teste que podemos escrever que nos forçará a mudar o código? Bem, temos evitado o caso em que o número é divisível por ambos 3 e 5. Vamos escrever agora.

Assert.Equal("FizzBuzz", FizzBuzz(15));

public String FizzBuzz(int n) {
    if (isDivisibleBy(3, n) && isDivisibleBy(5, n))
        return "FizzBuzz";
    if (isDivisibleBy(3, n))
        return "Fizz";
    if (isDivisibleBy(5, n))
        return "Buzz"
    return n.ToString();
}

Os testes passam, mas temos mais duplicação. Temos opções, mas vou aplicar "Extrair Variável Local" algumas vezes para refatorar em vez de reescrever.

public String FizzBuzz(int n) {

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

E cobrimos todas as contribuições razoáveis, mas e a entrada não razoável ? O que acontece se passarmos 0 ou um negativo? Escreva esses casos de teste.

public String FizzBuzz(int n) {

    if (n < 1)
        throw new InvalidArgException("n must be >= 1);

    var isDivisibleBy3 = isDivisibleBy(3, n);
    var isDivisibleBy5 = isDivisibleBy(5, n);

    if ( isDivisibleBy3 && isDivisibleBy5 )
        return "FizzBuzz";
    if ( isDivisibleBy3 )
        return "Fizz";
    if ( isDivisibleBy5 )
        return "Buzz"
    return n.ToString();
}

Isso está começando a parecer "código real" ainda? Mais importante, em que ponto deixou de ser "código irreal" e transição para ser "real"? Isso é algo para refletir sobre ...

Então, eu consegui fazer isso simplesmente procurando por um teste que eu sabia que não passaria em cada etapa, mas eu tive muita prática. Quando estou no trabalho, as coisas nem sempre são tão simples e nem sempre sei que teste vai forçar uma mudança. Às vezes, escrevo um teste e fico surpreso ao ver que ele já passa! Eu recomendo que você crie o hábito de criar uma "Lista de teste" antes de começar. Esta lista de testes deve conter todas as entradas "interessantes" que você pode imaginar. Você pode não usá-los todos e provavelmente adicionará casos, mas essa lista serve como um roteiro. Minha lista de testes para o FizzBuzz seria algo como isto.

  • Negativo
  • Zero
  • Um
  • Dois
  • Três
  • Quatro
  • Cinco
  • Seis (múltiplo não trivial de 3)
  • nove (3 ao quadrado)
  • Dez (múltiplo não trivial de 5)
  • 15 (múltiplo de 3 e 5)
  • 30 (múltiplo não trivial de 3 e 5)
por 25.07.2017 / 04:22
fonte
46

O código "real" é o código que você escreve para fazer seu teste passar. Realmente . É simples assim.

Quando as pessoas falam sobre escrever o mínimo para fazer o teste verde, isso significa que o seu código real deve seguir o YAGNI princípio .

A ideia do passo do refatorador é apenas limpar o que você escreveu quando estiver satisfeito por atender aos requisitos.

Desde que os testes que você escreve realmente incluam os requisitos do seu produto, uma vez que eles estejam passando, o código estará completo. Pense nisso, se todos os seus requisitos de negócios tiverem um teste e todos esses testes forem verdes, o que mais há para escrever? (Ok, na vida real nós não tendemos a ter cobertura de teste completa, mas a teoria é sólida.)

    
por 25.07.2017 / 00:21
fonte
14

A resposta curta é que o "código real" é o código que faz o teste passar. Se você puder fazer seu teste passar com algo diferente de código real, adicione mais testes!

Concordo que muitos tutoriais sobre o TDD são simplistas. Isso funciona contra eles. Um teste muito simples para um método que, digamos, computa 3 + 8, não tem outra escolha senão também computar 3 + 8 e comparar o resultado. Isso faz com que pareça que você estará duplicando todo o código, e que o teste é inútil, propenso a erros.

Quando você é bom em testes, isso informará como você estrutura seu aplicativo e como você escreve seu código. Se você tiver problemas para fazer testes úteis e sensatos, provavelmente deve repensar seu design um pouco. Um sistema bem projetado é fácil de testar - o que significa que testes sensatos são fáceis de serem pensados e implementados.

Quando você escreve seus testes primeiro, vê-os falhar e, em seguida, escreve o código que os faz passar, essa é uma disciplina para garantir que todo o seu código tenha testes correspondentes. Eu não cumpro servilmente essa regra quando estou codificando; muitas vezes escrevo testes depois do fato. Mas fazer testes primeiro ajuda a manter você honesto. Com alguma experiência, você começará a perceber quando estiver se transformando em um canto, mesmo quando não estiver escrevendo testes primeiro.

    
por 25.07.2017 / 00:21
fonte
6

Às vezes, alguns exemplos sobre o TDD podem ser enganosos. Como outras pessoas já apontaram antes, o código que você escreve para fazer os testes passarem é o código real.

Mas não pense que o código real parece mágica - isso é errado. Você precisa entender melhor o que deseja alcançar e, em seguida, precisa escolher o teste adequadamente, começando pelos casos mais fáceis e pelos casos de canto.

Por exemplo, se você precisa escrever um léxico, você começa com uma string vazia, então com um monte de espaços em branco, então um número, então com um número cercado por espaços em branco, um número errado, etc. leva você para o algoritmo certo, mas você não pula do caso mais fácil para um caso altamente complexo escolhido de forma idiota para obter o código real feito.

Bob Martin explica perfeitamente aqui .

    
por 25.07.2017 / 09:24
fonte
5

A parte do refatorado é limpa quando você está cansado e quer ir para casa.

Quando você está prestes a adicionar um recurso, a parte do refatorado é o que você altera antes do próximo teste. Você refatora o código para liberar espaço para o novo recurso. Você faz isso quando você sabe qual será esse novo recurso. Não quando você está apenas imaginando.

Isso pode ser tão simples quanto renomear GreetImpl para GreetWorld antes de criar uma classe GreetMom (depois de adicionar um teste) para adicionar um recurso que imprimirá "Hi Mom".

    
por 25.07.2017 / 03:04
fonte
1

Mas o código real apareceria no estágio de refatoração da fase TDD. Ou seja o código que deve fazer parte da versão final.

Os testes devem ser feitos toda vez que você fizer uma alteração.

O lema do ciclo de vida do TDD seria: REFRATOR VERDE VERMELHO

RED : Escreva os testes

GREEN : faça uma tentativa honesta de obter código funcional que passe nos testes o mais rápido possível: código duplicado, variáveis obscuramente chamadas de hacks da mais alta ordem, etc.

REFACTOR : Limpe o código, nomeie corretamente as variáveis. SECA o código.

    
por 25.07.2017 / 06:51
fonte
1

When do you write the “real” code in TDD?

A fase red é onde você escreve código.

Na fase refatoração , o objetivo principal é excluir o código.

Na fase red você faz qualquer coisa para fazer o teste passar o mais rápido possível e a qualquer custo . Você ignora completamente o que você já ouviu falar de boas práticas de codificação ou padrões de design semelhantes. Fazer o teste verde é tudo que importa.

Na fase refatoração , você limpa a bagunça que acabou de criar. Agora você primeiro olha se a mudança que você acabou de fazer é o tipo de top mais na lista de prioridades de transformação e se houver qualquer duplicação de código que você possa remover provavelmente aplicando um padrão de design.

Finalmente, você melhora a legibilidade renomeando identificadores e extrai números mágicos e / ou sequências literais para constantes.

It's not red-refactor, it's red-green-refactor. – Rob Kinyon

Obrigado por apontar isto.

Então é a fase verde onde você escreve o código real

Na fase red você escreve a especificação executável ...

    
por 26.07.2017 / 09:52
fonte
1

Você está escrevendo Real Code o tempo todo.

Em cada etapa Você está escrevendo código para satisfazer as condições que seu código satisfará para futuros chamadores do seu código (que pode ser você ou não ...).

Você acha que não está escrevendo código útil ( real ), porque em um momento você pode refatorá-lo.

Code-Refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behavior.

O que isto significa é que mesmo que Você esteja alterando o código, as condições que o código satisfez, são deixadas inalteradas. E as verificações ( testes ) Você implementou para verificar se Seu código já está lá para verificar se Suas modificações mudaram alguma coisa. Então o código que você escreveu o tempo todo está lá, apenas de uma maneira diferente.

Outra razão Você pode pensar que não é um código real, é que você está fazendo exemplos onde o programa final já pode ser previsto por você. Isso é muito bom, como mostra que você tem conhecimento sobre o domínio em que você está programando.
Mas muitas vezes os programadores estão em um domínio que é novo , desconhecido para eles. Eles não sabem qual será o resultado final e TDD é uma técnica para escrever programas passo a passo, documentando nosso conhecimento sobre como este sistema deve funcionar e verificando se código funciona dessa maneira.

Quando eu li The Book (*) no TDD, para mim a característica mais importante que se destacou foi a lista: TODO. Isso me mostrou que o TDD também é uma técnica para ajudar os desenvolvedores a se concentrarem em uma coisa por vez. Portanto, esta é também uma resposta à sua pergunta sobre Quanto código Real para escrever ? Eu diria código suficiente para me concentrar em uma coisa de cada vez.

(*) "Test Driven Development: por exemplo" por Kent Beck

    
por 27.07.2017 / 22:43
fonte
1

Você não está escrevendo código para fazer com que seus testes falhem.

Você escreve seus testes para definir como deve ser o sucesso, o que deve inicialmente falhar porque você ainda não escreveu o código que irá passar.

O ponto principal de escrever testes que falham inicialmente é fazer duas coisas:

  1. Cobrir todos os casos - todos os casos nominais, todos os casos de borda, etc.
  2. Valide seus testes. Se você só os vê passar, como pode ter certeza de que eles relatarão uma falha de forma confiável quando ocorrer um?

O ponto por trás do red-green-refactor é que escrever os testes corretos primeiro dá a você a confiança de saber que o código que você escreveu para passar nos testes está correto e permite refatorar com a confiança de que seus testes irão informá-lo assim que algo quebrar, você pode imediatamente voltar e consertar.

Na minha própria experiência (C # / .net), pure test-first é um ideal inatingível, porque você não pode compilar uma chamada para um método que ainda não existe. Portanto, "teste primeiro" é realmente sobre como codificar interfaces e implementações de stub primeiro, depois escrever testes contra os stubs (que inicialmente falharão) até que os stubs sejam apropriadamente desenvolvidos. Eu nunca estou escrevendo "código de falha", apenas construindo a partir de stubs.

    
por 31.07.2017 / 05:10
fonte
0

Acho que você pode estar confuso entre testes de unidade e testes de integração. Eu acredito que também pode haver testes de aceitação, mas isso depende do seu processo.

Depois de testar todas as pequenas "unidades", você as testa todas reunidas ou "integradas". Geralmente, é um programa ou biblioteca inteira.

No código que escrevi, a integração testa uma biblioteca com vários programas de teste que leem dados e os alimentam para a biblioteca, depois verificam os resultados. Então eu faço com threads. Então eu faço com threads e fork () no meio. Então eu corro e mato -9 após 2 segundos, então eu inicio e verifico o modo de recuperação. Eu confuso isso. Eu o torturo de todos os modos.

Tudo isso também está sendo testado, mas eu não tenho uma tela vermelha / verde bonita para os resultados. Ou ele consegue, ou eu cavo através de alguns milhares de linhas de código de erro para descobrir o porquê.

É aí que você testa o "código real".

E eu só pensei nisso, mas talvez você não saiba quando você deveria terminar de escrever testes de unidade. Você terminou de escrever testes de unidade quando seus testes exercitam tudo o que você especificou que deveria fazer. Às vezes, você pode perder a noção disso entre todos os casos de tratamento de erros e de borda, então talvez você queira fazer um bom grupo de teste de testes de caminho feliz que simplesmente vá direto às especificações.

    
por 27.07.2017 / 07:55
fonte
-6

Em resposta ao título da pergunta: "Quando você escreve o código" real "em TDD?", a resposta é: "quase nunca" ou "muito lentamente".

Você parece um aluno, então eu responderei como se estivesse aconselhando um aluno.

Você vai aprender muitas 'teorias' e 'técnicas' de codificação. Eles são ótimos para passar o tempo em cursos de estudantes superfaturados, mas de muito pouco benefício para você que você não pode ler em um livro na metade do tempo.

O trabalho de um codificador é apenas para produzir código. Código que funciona muito bem. É por isso que você, o programador planeja o código em sua mente, no papel, em um aplicativo adequado, etc., e planeja trabalhar com antecedência possíveis falhas / falhas, pensando lógica e lateralmente antes de codificar.

Mas você precisa saber como quebrar seu aplicativo para poder criar um código decente. Por exemplo, se você não sabia sobre Little Bobby Table (xkcd 327), então você provavelmente não estaria higienizando suas entradas antes de trabalhar com o banco de dados, então você não seria capaz de proteger seus dados em torno desse conceito.

O TDD é apenas um fluxo de trabalho projetado para minimizar os erros em seu código, criando os testes do que poderia dar errado antes de codificar seu aplicativo porque codificar pode se tornar exponencialmente difícil quanto mais código você introduzir e se esquecer de erros que você pensou . Assim que você achar que terminou sua inscrição, você executará os testes e o boom, com sorte, os bugs serão capturados com seus testes.

O TDD não é - como algumas pessoas acreditam - escrever um teste, fazer com que ele passe com o mínimo de código, escrever outro teste, conseguir passar com código mínimo, etc. Em vez disso, é uma maneira de ajudar você a codificar com confiança. Esse ideal de código contínuo de refatoração para fazê-lo funcionar com testes é idiota, mas é um conceito interessante entre os alunos, pois faz com que eles se sintam bem quando adicionam um novo recurso e ainda estão aprendendo a codificar ...

Por favor, não caia nessa armadilha e veja o seu papel de codificação para o que é - o trabalho de um codificador é apenas para produzir código. Código que funciona muito bem. Agora, lembre-se de que você estará no relógio como um codificador profissional, e seu cliente não se importará se você escreveu 100.000 asserções, ou 0. Eles só querem código que funcione. Muito bem, na verdade.

    
por 25.07.2017 / 22:33
fonte

Tags