Todos os exemplos que li e vi nos vídeos de treinamento têm exemplos simplistas. Mas o que eu não vejo se como faço o código "real" depois de ficar verde. Esta é a parte "Refatorar"?
Se eu tiver um objeto bastante complexo com um método complexo, e escrever meu teste e o mínimo necessário para fazê-lo passar (depois que ele falhar pela primeira vez, vermelho). Quando eu volto e escrevo o código real? E quanto código real eu escrevo antes de testar novamente? Suponho que o último seja mais intuição.
Edit: 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 exista, mas o que eu estava perguntando era: digamos que tenho um aplicativo para a construção de uma escola.
No meu design, tenho uma arquitetura com a qual quero começar, Histórias de usuários, e assim por diante. A partir daqui, pego essas histórias de usuário e crio um teste para testar a história de usuário. O usuário diz: Temos pessoas matriculadas na escola e pagamos taxas de inscrição. Então, penso em uma maneira de fazer isso falhar. Ao fazer isso, criei uma classe de teste para a classe X (talvez Student), que falhará. Eu então crio a classe "Aluno". Talvez "Escola" eu não sei.
Mas, de qualquer forma, o TD Design está me forçando a pensar na história. Se posso fazer um teste falhar, sei por que ele falha, mas isso pressupõe que eu possa fazer 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 realmente acompanhar isso em sua mente, mas, na realidade, a parte mais difícil é saber quando a recursão "quebra", quando parar (minha opinião, é claro). Então, tenho que pensar no que para a recursão primeiro. É apenas uma analogia imperfeita, e assume que cada iteração recursiva é um "passe". Mais uma vez, apenas uma opinião.
Na implementação, a escola é mais difícil de ver. Os ledgers 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, tenho que pensar mais sobre como implementá- lo. Eu tenho o conceito de falha, aprovação, refatoração (principalmente por causa do estudo e dessa pergunta).
O que eu não sei é baseado na falta de experiência, na minha opinião. 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, não sei se estou apenas testando para ver se recebo de volta um ID exclusivo do banco de dados ou outra coisa quando alguém digita um nome em um banco de dados ou ambos ou nenhum.
Ou, talvez isso mostre que ainda estou confuso.
Respostas:
Você não "volta" e escreve "código real". É tudo código real. O que você faz é voltar e adicionar outro teste que o força a alterar seu código para fazer o novo teste passar.
Quanto código você escreve antes de testar novamente? Nenhum. Você escreve código zero sem um teste com falha que o força a escrever mais código.
Observe o padrão?
Vamos examinar outro exemplo simples, na esperança de que isso ajude.
Fácil fácil.
Não é o que você chamaria de código real, certo? Vamos adicionar um teste que força uma mudança.
Poderíamos fazer algo bobo
if n == 1
, mas pularemos para a solução sã.Legal. Isso funcionará para todos os números que não sejam do FizzBuzz. Qual é a próxima entrada que forçará a alteração do código de produção?
E de novo. Escreva um teste que ainda não passou.
E agora cobrimos todos os múltiplos de três (que também não são múltiplos de cinco, observaremos e voltaremos).
Ainda não escrevemos um teste para o "Buzz", então vamos escrever isso.
E, novamente, sabemos que há outro caso que precisamos resolver.
E agora podemos lidar com todos os múltiplos de 5 que também não são múltiplos de 3.
Até o momento, ignoramos a etapa de refatoração, mas vejo alguma duplicação. Vamos limpar isso agora.
Legal. Agora removemos a duplicação e criamos uma função bem nomeada. Qual é o próximo teste que podemos escrever que nos forçará a alterar o código? Bem, evitamos o caso em que o número é divisível por 3 e 5. Vamos escrever agora.
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.
E cobrimos todas as informações razoáveis, mas e as informações irracionais ? O que acontece se passarmos 0 ou negativo? Escreva esses casos de teste.
Isso já está começando a parecer "código real"? Mais importante, em que ponto deixou de ser "código irreal" e passou a ser "real"? Isso é algo para refletir ...
Então, pude fazer isso simplesmente procurando por um teste que sabia que não passaria a cada passo, mas tive muita prática. Quando estou no trabalho, as coisas nem sempre são tão simples e nem sempre sei qual teste forçará uma mudança. Às vezes, escrevo um teste e fico surpreso ao ver que ele já passou! Eu recomendo que você adquira o hábito de criar uma "Lista de testes" antes de começar. Esta lista de teste deve conter todas as entradas "interessantes" que você puder imaginar. Você pode não usá-los todos e provavelmente adicionará casos à medida que avança, mas essa lista serve como um roteiro. Minha lista de testes para o FizzBuzz se pareceria com isso.
fonte
O código "real" é o código que você escreve para fazer seu teste passar. Sério . É simples assim.
Quando as pessoas falam sobre escrever o mínimo necessário para tornar o teste verde, isso significa apenas que seu código real deve seguir o princípio YAGNI .
A idéia da etapa de refatoração é apenas para limpar o que você escreveu quando estiver satisfeito por atender aos requisitos.
Desde que os testes que você escreve realmente abranjam os requisitos do seu produto, uma vez que eles são aprovados, o código está completo. Pense nisso, se todos os seus requisitos de negócios tiverem um teste e todos esses testes forem ecológicos, o que mais há para escrever? (Ok, na vida real, não costumamos ter uma cobertura completa de testes, mas a teoria é sólida.)
fonte
switch
com um caso para cada teste de unidade que passaria em todos os testes e falharia em outras entradas.switch
" foi concebido como um extremo lógico de "escrever o mínimo necessário para tornar os testes verdes". Eu vejo a pergunta do OP como: onde no TDD está o princípio que evita esse tamanhoswitch
?A resposta curta é que o "código real" é o código que faz o teste passar. Se você pode fazer seu teste passar com algo diferente de código real, adicione mais testes!
Concordo que muitos tutoriais sobre TDD são simplistas. Isso funciona contra eles. Um teste muito simples para um método que, digamos, calcula 3 + 8 realmente não tem escolha, mas também computa 3 + 8 e compara o resultado. Isso faz com que pareça que você duplicará todo o código, e esse teste é um trabalho extra inútil e propenso a erros.
Quando você é bom em testes, isso informa como você estrutura seu aplicativo e como você escreve seu código. Se você tiver problemas para apresentar testes úteis e sensíveis, provavelmente deve repensar um pouco o seu design. Um sistema bem projetado é fácil de testar - ou seja, é fácil pensar e implementar testes sensíveis.
Quando você escreve seus testes primeiro, assiste-os a falhar e depois escreve o código que os faz passar, é uma disciplina para garantir que todo o seu código tenha testes correspondentes. Não sigo servilmente essa regra quando estou codificando; muitas vezes escrevo testes após o fato. Mas fazer os testes primeiro ajuda a mantê-lo honesto. Com alguma experiência, você começará a perceber quando estiver se codificando em um canto, mesmo quando não estiver escrevendo testes primeiro.
fonte
assertEqual(plus(3,8), 11)
, nãoassertEqual(plus(3,8), my_test_implementation_of_addition(3,8))
. Para casos mais complexos, você sempre procura meios de provar o resultado correto, além de calcular dinamicamente o resultado correto no teste e verificar a igualdade.plus(3,8)
retornou o resultado correto subtraindo 3 dele, subtraindo 8 dele e verificando o resultado contra 0. Isso é tão obviamente equivalenteassertEqual(plus(3,8), 3+8)
a ser um um pouco absurdo, mas se o código em teste está criando algo mais complicado do que apenas um número inteiro, pegar o resultado e verificar a exatidão de cada parte geralmente é a abordagem correta. Como alternativa, algo parecido comfor (i=0, j=10; i < 10; ++i, ++j) assertEqual(plus(i, 10), j)
plus()
pode adicionar 10 às coisas. Ainda dependemos dos valores do loop inicial verificados pelo programador, é claro.Às vezes, alguns exemplos sobre TDD podem ser enganosos. Como outras pessoas 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 - está errado. Você precisa entender melhor o que deseja alcançar e, em seguida, escolher o teste de acordo, começando pelos casos mais fáceis e de canto.
Por exemplo, se você precisar escrever um lexer, comece com uma string vazia, depois com um monte de espaços em branco, depois um número, depois com um número cercado por espaços em branco, depois um número errado etc. Essas pequenas transformações levarão você a o algoritmo certo, mas você não pula do caso mais fácil para um caso altamente complexo, escolhido de maneira tola para realizar o código real.
Bob Martin explica perfeitamente aqui .
fonte
A parte do refator é limpa quando você está cansado e quer ir para casa.
Quando você está prestes a adicionar um recurso, a parte do refator é 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 sabe qual será o novo recurso. Não quando você está apenas imaginando.
Pode ser tão simples quanto renomear
GreetImpl
paraGreetWorld
antes de criar umaGreetMom
classe (após adicionar um teste) para adicionar um recurso que imprimirá "Oi mãe".fonte
Mas o código real apareceria no estágio de refatoração da fase TDD. Ou seja, o código que deve fazer parte do lançamento final.
Os testes devem ser executados toda vez que você fizer uma alteração.
O lema do ciclo de vida do TDD seria: REFINADOR VERDE VERMELHO
VERMELHO : Escreva os testes
VERDE : 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 com nomes obscuros, hacks da mais alta ordem, etc.
REFATOR : Limpe o código, nomeie corretamente as variáveis. SECAR o código.
fonte
A fase vermelha é onde você escreve o código.
Na fase de refatoração , o objetivo principal é excluir o código.
Na fase vermelha , você faz qualquer coisa para tornar o teste o mais rápido possível e a qualquer custo . Você ignora completamente o que já ouviu falar de boas práticas de codificação ou de padrões de design. Tornar o teste verde é tudo o que importa.
Na fase de refatoração , você limpa a bagunça que acabou de fazer. Agora, verifique primeiro se a alteração que você acabou de fazer é do tipo mais importante na lista Prioridade de Transformação e se existe alguma duplicação de código que você pode remover com maior probabilidade aplicando um padrão de design.
Finalmente, você melhora a legibilidade renomeando identificadores e extrai números mágicos e / ou seqüências literais para constantes.
Obrigado por apontar para isso.
Portanto, é a fase verde em que você escreve o código real
Na fase vermelha , você escreve a especificação executável ...
fonte
Você está escrevendo Código Real o tempo todo.
Em cada etapa, você está escrevendo um código para satisfazer as condições que o seu código atenderá aos futuros chamadores do seu código (que podem ser você ou não ...).
Você pensa que não está escrevendo código útil ( real ), porque em um momento você pode refatorá-lo.
O que isso significa é que, embora Você esteja alterando o código, as condições atendidas pelo código permanecem inalteradas. E as verificações ( testes ) que você implementou para verificar se seu código já estão 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.
Outro motivo para você pensar que não é um código real, é que você está fazendo exemplos em que o programa final já pode ser previsto por você. Isso é muito bom, pois mostra que você tem conhecimento sobre o domínio que você está programando em.
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 o TDD é uma técnica para escrever programas passo a passo, documentando nosso conhecimento sobre como esse sistema deve funcionar e verificando se nosso código funciona dessa maneira.
Quando li The Book (*) no TDD, para mim, o recurso 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 de cada vez. Portanto, esta também é uma resposta para sua pergunta sobre quanto código real escrever ? Eu diria código suficiente para focar em uma coisa de cada vez.
(*) "Test Driven Development: By Example" de Kent Beck
fonte
Você não está escrevendo código para fazer seus testes falharem.
Você escreve seus testes para definir como deve ser o sucesso, o que deve falhar inicialmente, porque você ainda não escreveu o código que será aprovado.
O ponto principal sobre a criação de testes com falha inicial é fazer duas coisas:
O ponto por trás do refator vermelho-verde é 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 o informarão assim que algo quebra, para que você possa voltar imediatamente e consertá-lo.
Na minha própria experiência (C # /. NET), o puro teste primeiro é um ideal inatingível, porque você não pode compilar uma chamada para um método que ainda não existe. Portanto, "testar primeiro" é realmente sobre a codificação de interfaces e implementações de stub, primeiro, e depois a gravação de testes nos stubs (que falharão inicialmente) até que os stubs sejam aprimorados adequadamente. Eu nunca estou escrevendo "código com falha", apenas construindo a partir de stubs.
fonte
Eu acho que você pode estar confuso entre testes de unidade e testes de integração. 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 testará todas montadas 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 lêem os dados e os alimentam na biblioteca, depois verifica os resultados. Então eu faço isso com threads. Então eu faço isso com threads e fork () no meio. Então eu o executo e mato -9 após 2 segundos, inicio e verifico seu modo de recuperação. Eu confuso. Eu o torturo de todos os tipos.
Tudo isso também está sendo testado, mas não tenho uma exibição bonita de vermelho / verde para os resultados. Ou é bem-sucedido ou vasculho alguns milhares de linhas de código de erro para descobrir o porquê.
É aí que você testa o "código real".
E eu pensei nisso, mas talvez você não saiba quando deve escrever testes de unidade. Você terminou de escrever testes de unidade quando seus testes exercitam tudo o que você especificou que deve fazer. Às vezes, você pode perder o controle disso entre todos os casos de erros e manipulação de borda; portanto, convém fazer um bom grupo de testes de testes de caminho feliz que simplesmente seguem as especificações.
fonte
Em resposta ao título da pergunta: "Quando você escreve o código" real "no TDD?", A resposta é: 'quase nunca' ou 'muito lentamente'.
Você parece um estudante, 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 para estudantes muito caros, mas de muito pouco benefício para você que você não conseguia ler em um livro na metade do tempo.
O trabalho de um codificador é apenas produzir código. Código que funciona muito bem. É por isso que você, o codificador, planeja o código em sua mente, no papel, em um aplicativo adequado etc., e planeja solucionar possíveis falhas / buracos com antecedência, pensando lógica e lateralmente antes da codificação.
Mas você precisa saber como quebrar seu aplicativo para criar um código decente. Por exemplo, se você não soubesse da Little Bobby Table (xkcd 327), provavelmente não limparia suas entradas antes de trabalhar com o banco de dados, para que não pudesse proteger seus dados com base nesse conceito.
O TDD é apenas um fluxo de trabalho projetado para minimizar os bugs no seu código, criando os testes do que poderia dar errado antes de você codificar seu aplicativo, porque a codificação pode ficar exponencialmente difícil quanto mais código você introduzir e você esquecer os bugs que antes pensava. Depois que você conclui o aplicativo, executa os testes e o boom, esperamos que os erros sejam detectados nos seus testes.
TDD não é - como algumas pessoas acreditam - escrever um teste, passar com código mínimo, escrever outro teste, passar com código mínimo, etc. Em vez disso, é uma maneira de ajudá-lo a codificar com confiança. Esse ideal de código de refatoração contínua para fazer funcionar com testes é idiota, mas é um bom conceito entre os alunos, porque os faz se sentir bem quando adicionam um novo recurso e ainda aprendem a codificar ...
Por favor, não caia nessa armadilha e veja seu papel de codificar para o que é - o trabalho de um codificador é apenas produzir código. Código que funciona muito bem. Agora, lembre-se de que você estará no relógio como codificador profissional, e seu cliente não se importará se você escreveu 100.000 afirmações ou 0. Eles querem apenas um código que funcione. Muito bem, de fato.
fonte