tdd

Aprendendo TDD (ou Desenvolvimento Orientado a Testes)

Fala pessoal, tudo bom? Quanto tempo!

Recentemente tenho trabalho muito em vários projetos e estou com duas turmas do Assando Sites, o que me deixa praticamente sem tempo pro blog ou qualquer outra coisa.

Na última semana, comecei a ler o livro TDD – Desenvolvimento Guiado por Testes, escrito por Kent Beck e publicado pela Bookman.

O livro é sensacional, na primeira parte ele mostra o passo a passo do desenvolvimento (baseado em TDD, claro) de um mecanismo de conversão monetária. Na segunda parte do livro ele me surpreendeu: começa a criar um framework de testes, usando TDD (claro), porém usando o PRÓPRIO framework de testes pra testar a si mesmo.

O autor do livro é considerado um dos criadores do TDD, então o cara sabe do que tá falando.

Mesmo recomendando muito o livro, não estou aqui pra falar dele e sim do assunto principal do livro: TDD.

Não sou um adepto fanático do TDD

Eu adoraria, mas ainda não consegui aplicar TDD em cada projeto que trabalho. Conheço bem e concordo MUITO com a metologia por trás do TDD, mas ainda consigo usá-lo naturalmente, tenho que me forçar sempre que quero testar algo.

Mas afinal, que diabos é TDD?

Me surpreende que a maioria dos desenvolvedores e programadores por ai não saibam o que é TDD… eu mesmo não sabia muito TDD uns 2 anos atrás… e já estou no mercado a mais de 10 anos, imagina quem está começando agora.

Segundo a Wikipédia, TDD é:

Test Driven Development (TDD) ou em português Desenvolvimento dirigido guiado por testes é uma técnica de desenvolvimento de software que baseia em um ciclo curto de repetições:

Primeiramente o desenvolvedor escreve um caso de teste automatizado que define uma melhoria desejada ou uma nova funcionalidade. Então, é produzido código que possa ser validado pelo teste para posteriormente o código ser refatorado para um código sob padrões aceitáveis.

Kent Beck, considerado o criador ou o ‘descobridor’ da técnica, declarou em 2003 que TDD encoraja designs de código simples e inspira confiança.

E qual a vantagem disso?

Pra mim, TDD é uma forma de você garantir que, através de pequenos passos, você vai chegar à um “todo-completo”, que é uma aplicação que funciona baseado apenas nas especificações que foram definidas, e lá na frente quando você for mudar algo, é só rodar os testes novamente para garantir que tudo continue funcionando.

Mas por via das dúvidas, pesquisem mais sobre o assunto, tem um mundo por trás disso tudo.

“Talk is cheap! Shut up and show me the code!”

(eu amo essa frase)

Vamos direto ao ponto e vamos ver um pequeno exemplo de como criar uma função baseada em TDD… e vamos começar pelo exemplo mais simples e usado por todos: o famoso Fizz Buzz, que segundo a nossa amiga Wikipédia é:

Bizz buzz (also known as fizz buzz, or simply buzz) is a group word game frequently encountered as a drinking game. Players take turns to count incrementally, replacing any number divisible by three with the word “bizz”, and any number divisible by five with the word “buzz”.

Tradução deste que vos fala:

Bizz buzz (também conhecido com fizz buzz, ou apenas buzz) é uma brincadeira em grupo, comummente jogada como desculpa pra encher a cara. Os jogadores jogam em turnos incrementais, onde cada um fala um número substituindo números divisíveis por três pela palavra “fizz“, e números divisíveis por cinco pela palavra “buzz“.

Sendo que você precisa falar “fizz buzz” quando o número for múltiplo de três e de cinco.

Básicamente o resultado final do jogo seria algo assim:

1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, Fizz Buzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26, Fizz, 28, 29, Fizz Buzz, 31, 32, Fizz, 34, Buzz, Fizz, …

Então vamos criar uma função FizzBuzz que receba um parâmetro N e retorne o número, fizz, buzz ou fizz buzz dependendo do número que ela recebeu. :)

Antes de qualquer coisa, vou fazer algo que aprendi com esse livro e recomendo muito: vamos montar uma lista de especificações que precisaremos testar/implementar.

  • FizzBuzz recebe um número
  • FizzBuzz(1) retorna 1
  • FizzBuzz(2) retorna 2
  • FizzBuzz(3) retorna “fizz”
  • FizzBuzz(4) retorna 4
  • FizzBuzz(5) retorna “buzz”

Essas não são todas as regras do jogo, mas já é um bom começo… Cada um desses itens vai significar um teste e – possivelmente – uma melhoria no código da função.

Ao longo desse artigo irei copiar essa lista diversas vezes, marcando com negrito os itens que iremos atacar, e riscando os itens que forem concluídos.

Mas peraí… e a linguagem?

Verdade… temos que decidir uma linguagem, e como eu falo MUITO de PHP aqui no blog vamos fazer esse FizzBuzz em Python, que já vem com um framework de testes embutido na linguagem, pra quem quiser brincar de testes unitários no PHP recomendo muito o PHPUnit.

Baby steps, ou “Passos de bebê”

Uma das maiores características do desenvolvimento orientado à testes é que você sempre tente dar passos menores que suas pernas, não significa que você não possa dar uma corrida se o projeto exigir, mas sempre avance com pequenos passos, nada de escrever 100 linhas de código sem testar (com testes)… e eu vou tentar seguir essa metodologia aqui.

Mãos à obra!

No TDD você SEMPRE começa pelo teste, então vamos começar criando nosso arquivo de testes:

Fizemos três coisas no nosso arquivo fizzbuzz_test.py:

  1. Primeiro temos #!/usr/bin/env python que permite que executemos o arquivo sem ser através do executável do Python, mas isso é opcional.
  2. Depois importamos a biblioteca nativa de testes unitários do Python
  3. E por fim usamos uma condição que – resumidamente – permite que o arquivo seja executado pelo terminal já rodando os testes

Quando a gente rodar esse arquivo com o comando ./fizzbuzz_test.py ou o comando python fizzbuzz_test.py vamos ter o seguinte output:

Então sabemos que tudo está funcionando… prontos para o primeiro teste?

  • FizzBuzz recebe um número
  • FizzBuzz(1) retorna 1
  • FizzBuzz(2) retorna 2
  • FizzBuzz(3) retorna “fizz”
  • FizzBuzz(4) retorna 4
  • FizzBuzz(5) retorna “buzz”

Nosso primeiro teste, da forma mais simples e reduzida possível, ficaria assim:

Criamos uma classe TestFizzBuzz, que é um caso de teste (contém vários testes) para testar a classe/funcionalidade FizzBuzz.

Definimos nosso primeiro teste (test_FizzBuzz), onde fizemos uma asserção (verificação):

O resultado de FizzBuzz(1) é IGUAL a 1?

A asserção de igualdade é justamente o método assertEqual… :)

Prontos pra rodar o teste? Vamos ver o que acontece…

UHU! Nosso primeiro erro! (sim, no TDD os testes não passando significam progresso).. mas não é o erro que eu estava esperando! :(

O problema é que nós ainda não definimos FizzBuzz, por isso o Python xiou dizendo “global name ‘FizzBuzz’ is not defined“.

Vamos criar nosso arquivo fizzbuzz.py com o seguinte conteúdo:

Criamos a estrutura da nossa função FizzBuzz que ainda não faz nada.

O “pass” no Python é usado quando uma função/método ainda não tem conteúdo.. e como não temos chaves pra dizer onde ela começa e termina, precisamos de uma instrução que faça exatamente nada.

Vamos voltar ao nosso teste e importar essa função para que ela possa ser usada nos nossos testes, depois vamos rodar os testes novamente e ver se aquela mensagem de erro mudou.

A linha “from fizzbuzz import FizzBuzz” significa “Importe a classe/função FizzBuzz do arquivo ou módulo chamado fizzbuzz“… isso mesmo, no Python podemos importar apenas parte de um arquivo! :)

E o resultado da execução dos testes é…

E estamos chegando lá… agora o Python reclamou que – segundo sua definição – a função FizzBuzz não recebe parâmetros.. que é justamente o primeiro item da nossa lista, então vamos fazer isso acontecer.

Isso.. tudo beeeeem de vagar, lembre-se dos passos de bebê!

Agora rodamos os testes novamente e…

Conseguimos! O primeiro item da lista foi resolvido! :D

  • FizzBuzz recebe um número
  • FizzBuzz(1) retorna 1
  • FizzBuzz(2) retorna 2
  • FizzBuzz(3) retorna “fizz”
  • FizzBuzz(4) retorna 4
  • FizzBuzz(5) retorna “buzz”

Mas agora temos outro problema… que é justamente a nossa asserção de “FizzBuzz(1) é 1″ falhando.

Fazendo um teste passar

Agora vem a parte (pra mim) mais importante do TDD:

Sempre que você escrever um teste e ele quebrar, pergunte-se: “Qual o menor passo, a menor mudança no código, que eu posso fazer pra esse teste passar?” Não importa se esse passo é elegante, segue padrões de projeto ou está simplesmente enganando o código… A primeira vez que você faz o teste passar tem a ver com velocidade e simplicidade, boas práticas fica pro momento da refatoração, com todos os testes passando.

A menor mudança que a gente pode fazer pra esse código funcionar, sem pensar nos outros casos de FizzBuzz(n) que ainda não estão testados é:

Você pode querer me matar depois dessa, mas não estou brincando.. isso é sério, é assim que a coisa funciona! :)

E os testes?

Satisfação! Finalmente, nosso primeiro teste passou!!!

  • FizzBuzz recebe um número
  • FizzBuzz(1) retorna 1
  • FizzBuzz(2) retorna 2
  • FizzBuzz(3) retorna “fizz”
  • FizzBuzz(4) retorna 4
  • FizzBuzz(5) retorna “buzz”

Decidindo o próximo teste

Quando você estiver decidindo o próximo teste, tente seguir as seguintes dicas:

  • Escreva um teste que você SABE não vai passar.. Não adianta ficar testando coisas que já estão passando, né?
  • Mas se você não tem certeza, escreva o teste e veja o que acontece… Testes nunca são demais.
  • Escreva um teste que você ACHA que pode ser resolvido de forma simples, nada de testar o programa todo de uma vez.. o ideal é que você tenha apenas um teste quebrando em cada “rodada”
  • Teste com valores plausíveis e facilmente compreensíveis… testar soma (1, 2) == 3 é muito melhor do que testar soma (12312512312, 31653341265312) = ????, entendeu onde quero chegar?

Então vamos seguir a lista e testar FizzBuzz(2) que deveria retornar 2, e provavelmente não vai passar por causa do nosso roubo (return 1).

Eu poderia fazer a nova asserção dentro do mesmo teste, mas preferi trocar o nome dele e criar um segundo teste, assim as coisas ficam mais claras e você pode ver cada teste falhando separadamente.

E com esse teste, concluímos que ao roubar (mesmo valendo pra quele momento) acabamos cuspindo pra cima, e agora o cuspe caiu na nossa cabeça… 2 (esperado) é diferente de 1 (resultado).

Mais uma vez, hora de se perguntar: “Qual o menor passo, a menor mudança no código, que eu posso fazer pra esse teste passar?“.. e se a função retornar o número que recebeu?

Feita a mudança, rodamos os testes e…

Excelente! Nosso segundo teste está passando e, quase sem perceber, refatoramos o código para algo realmente dentro das regras do problema :)

  • FizzBuzz recebe um número
  • FizzBuzz(1) retorna 1
  • FizzBuzz(2) retorna 2
  • FizzBuzz(3) retorna “fizz”
  • FizzBuzz(4) retorna 4
  • FizzBuzz(5) retorna “buzz”

Qual o nosso próximo teste? Vamos seguir a lista e ver no que vai dar… Se entendemos bem o que fizemos até agora, FizzBuzz(3) vai retornar 3 e não “fizz” como manda a especificação do problema (múltiplos de três retornam “fizz”).

Como voltamos na especificação do problema, vamos adicionar mais um item à nossa lista:

  • FizzBuzz(9) retorna “fizz”

Agora vamos ao teste do FizzBuzz(3) retorna “fizz”:

E o resultado dos testes, como esperado, falhou:

Minha função FizzBuzz ainda não está preparada para retornar fizz, o teste quebrou e nós progredimos em direção a solução de mais um problema… viu como é legal?

Eu sei que você está querendo começar a correr e verificar se numero é múltiplo de três, mas não temos testes pra isso ainda.. temos apenas um teste onde FizzBuzz(3) deveria retornar “fizz”… é esse pequeno passo que vamos dar. TDD também tem a ver com ansiedade, e você precisa aprender a controlar a sua.

E os testes passaram! :D

  • FizzBuzz recebe um número
  • FizzBuzz(1) retorna 1
  • FizzBuzz(2) retorna 2
  • FizzBuzz(3) retorna “fizz”
  • FizzBuzz(4) retorna 4
  • FizzBuzz(5) retorna “buzz”
  • FizzBuzz(9) retorna “fizz”

Eu poderia seguir a lista e partir para o FizzBuzz(4) mas ele provavelmente vai passar, mas ao mesmo tempo, entre a dúvida e o teste, fique com o teste:

Ok.. os testes continuam passando, então esse teste não colaborou em nada para o problema.. Vamos dar um pulo e testar nosso caso mais recente:

  • FizzBuzz recebe um número
  • FizzBuzz(1) retorna 1
  • FizzBuzz(2) retorna 2
  • FizzBuzz(3) retorna “fizz”
  • FizzBuzz(4) retorna 4
  • FizzBuzz(5) retorna “buzz”
  • FizzBuzz(9) retorna “fizz”

Ok.. os testes quebraram.. mas como vamos resolver o problema?

Sabemos que nosso código ainda tem um “roubo”, podemos fazer outro roubo ou partir para uma refatoração que resolva o FizzBuzz(3) e FizzBuzz(9), como o problema é ridiculamente simples e esse artigo está ficando longo demais, vamos pra segunda opção:

E os testes passaram! :)

Vejam que foi uma refatoração bem simples, ao invés de verificar se o número é igual a três, eu verifiquei se não sobrou resto da sua divisão por três, ou seja: se ele é múltiplo de três.

Uma coisa que está me incomodando um pouco são todos os nomes testes, acho que podemos refatorar o teste a grupar casos semelhantes:

Agora sim! Bem melhor, e continuamos testando a mesma coisa… só que com menos testes e mais asserções por teste.

Nossa lista está quase acabando…

  • FizzBuzz recebe um número
  • FizzBuzz(1) retorna 1
  • FizzBuzz(2) retorna 2
  • FizzBuzz(3) retorna “fizz”
  • FizzBuzz(4) retorna 4
  • FizzBuzz(5) retorna “buzz”
  • FizzBuzz(9) retorna “fizz”

Falta apenas o FizzBuzz(5) mas acho que é hora de revisar o problema e adicionar mais alguns itens à lista:

  • FizzBuzz(5) retorna “buzz”
  • FizzBuzz(10) retorna “buzz”
  • FizzBuzz(15) retorna “fizzbuzz”
  • FizzBuzz(30) retorna “fizzbuzz”

Claro que eu já sei como o problema funciona, mas podemos levantar esses exemplos só de olhar pra descrição do problema lá em cima..

Então vamos partir pro primeiro item da lista: FizzBuzz(5) retorna “buzz”

Os testes voltam a quebrar, apenas o último teste falhou pois 5 != “buzz”.

Como ainda não explicitamos a regra do “multiplo de 5″ através de testes, o correto aqui é fazer com que apenas essse teste passe (e não testes futuros):

E todos os testes passaram! :D

Agora vamos colocar mais um múltiplo de cinco, como por exemplo FizzBuzz(10) e o teste vai quebrar:

Vamos então parar de roubar e fazer o que fizemos com com os múltiplos de três:

E todos os três testes (com as 7 asserções) passaram novamente.

  • FizzBuzz(5) retorna “buzz”
  • FizzBuzz(10) retorna “buzz”
  • FizzBuzz(15) retorna “fizzbuzz”
  • FizzBuzz(30) retorna “fizzbuzz”

Agora vamos atacar a última parte do problema, múltiplos de três e de cinco.

Nada de escrever código, primeiro o teste:

(Aproveitei também pra mudar o nome dos testes)

Vejam vocês… o teste falhou.. mas diferente do que vimos até agora, não temos “15 != fizzbuzz” mas sim “fizz != fizzbuzz” pois 15 é multiplo de 3, correto?

Primeiro roubamos (o que o Kent Beck chama de “implementação óbvia”):

E todos os testes voltam a passar… Agora adicionamos mais um teste da nossa lista:

  • FizzBuzz(5) retorna “buzz”
  • FizzBuzz(10) retorna “buzz”
  • FizzBuzz(15) retorna “fizzbuzz”
  • FizzBuzz(30) retorna “fizzbuzz”

E fazemos uma implementação simples, que resolva o problema:

Agora, que todos os testes estão passando e o problema está resolvido, podemos refatorar nossa função pra algo mais elegante:

Não que essa solução seja a mais elegante e mais eficiente em Python, mas acredito que ela deixe a lógica mais clara.

Enfim… TDD é isso!

Espero que vocês tenham gostado. :D

13 ideias sobre “Aprendendo TDD (ou Desenvolvimento Orientado a Testes)

  1. Pingback: TDD com Python: Como aprender de forma certa | Aprendendo Python

  2. Pingback: Criando um sistema de login com PHP e MySQL | Thiago Belem / Blog

  3. Pingback: Aprendendo TDD (ou Desenvolvimento Orientado a Testes) Python « new Blog();

  4. Victor

    Isso ai, Thiago, mais cara eu acho que você esta fazendo a faculdade errada ….rsrs
    Você já é muito bom em programação, acho que você deveria ter feito publicidade, o que conta mais com essa questão de design, fora que com a quantidade de agências de publicidade que existe em São Paulo, com esse conhecimento, você iria com certeza passar da média dos 1.200,00, eu acho, né
    Mais mesmo assim parabéns pelo blog…

  5. Julio Bitencourt

    Parabéns Thiago. Eu gosto destes artigos que ‘pegam pela mão’ e ensinam quem não sabe absolutamente nada sobre o assunto. Isto ajuda também quem não é programador, mas trabalha com gerência de projetos e precisa contratar um profissional.
    Estamos com esta abordagem também em nosso blog.

  6. Daniel Costa

    Show de bola! Bendita hora que assinei os informativos do seu blog! Muito bem explicado. Ainda não trabalho com TDD, mas já tinha ouvido falar e tinha interesse em entender um pouco mais sobre o assunto. O seu artigo veio como um poderoso incentivo neste sentido. Valeu!

  7. Thiago Gonzalez

    Realmente, ótimo post.
    Conversando contigo a respeito, você me disse que estão pensando em aplicar TDD para desenvolvimento front-end.
    Lendo seu post, os passos que você deu, eu não consigo imaginar como isso seria aplicado a HTML/CSS de forma produtiva.

    Posso pensar em testes do tipo navegadores x bloco de código.

    Como por exemplo:

    Header -> testar em todos navegadores
    Bordas arredondadas -> Rollback para IE 7 –

    Era esse tipo de teste que você tinha em mente?
    Parabéns :)

  8. Wanderson Valério

    Ótimo post Thiago, espero que agora que está menos ocupado, continue com os ótimos posts de sempre! :D

Os comentários estão fechados.