Aprendendo TDD! O pensamento de Desenvolvimento Guiado por Testes

Uma das partes mais difíceis de fazer TDD é pensar em um código que não existe. Como você chama o método? Como vai fazer as asserções pra garantir que ele funciona?

Na verdade essa reflexão sobre o código que ainda está por vir é que é o maior benefício do TDD! Fazer essas perguntas sem ter tido todo o trabalho de escrever o código é uma grande ajuda.

O que pensamos quando estamos escrevendo o código

Ao escrever o código de produção eu me concentro muito mais no estilo da escrita, no tamanho que o método está ficando, se posso extrair alguma parte desse método etc.

Estamos pensando em fazer certo o código que estamos desenvolvendo. A visão fica mais estreita e muitas vezes coisas óbvias acabam passando totalmente desapercebido, como declarar uma variável chamada p, ou um if else para retornar true ou false.

Isso não é algo ruim, quando estamos escrevendo um código precisamos estar focados. O ponto é ter um balanço entre o foco em algo bem específico e a visão do todo, para garantir que não só estamos resolvendo o problema corretamente como estamos resolvendo o problema correto.

Alguns estudos expõe essa nossa capacidade muito bem. Esse vídeo é um excelente exemplo de exercício de foco: https://www.youtube.com/watch?v=IGQmdoK_ZfY.

O que pensamos quando estamos escrevendo os testes

Quando mudamos nossa atenção para escrever os testes devemos pensar em como o código, que ainda não existe, será utilizado.

Essa mudança de pensamento tem um efeito interessante. Tente responder o seguinte exercício que é bem conhecido nos estudos de psicologia cognitiva:

  • Um taco e uma bola de baseball custam juntos U$1.10.
  • O taco custa um dólar a mais que a bola.
  • Quanto custa a bola?

Se você pensou que a resposta é U$0.10, você errou, mas não está só. Eu também pensei que a resposta seria o mais óbvio, mas se a bola custasse U$0.10, então a soma seria U$1.20.

Escrever testes ajuda a evitar que esse pensamento rápido e focado em resolver o problema na nossa frente acabe por nos fazer errar. Se você tivesse um teste para essa situação, ao colocar o valor da bola como R$0.10, o teste falha e você recebe o feedback. Fabio Pereira explora muito mais esse assunto no capítulo “Psicologia cognitiva explicando Ágil” do livro ThoughtWorks Antologia Brasil.

Desenvolvendo Guiado por Testes

Vamos analisar agora as várias decisões de design que tomamos ao escrever os testes primeiro. Veja o exemplo de teste a seguir:

require 'minitest/autorun'
require 'minitest/spec'
require_relative '../lib/quadro.rb'

describe Quadro do
  it "realiza jogada na posicao especificada" do
    quadro = Quadro.new
    quadro.executar_jogada('x', 1, 1)
    quadro.posicoes[1][1].must_equal 'x'
  end
end

O objeto quadro é responsável por realizar as jogadas, mas precisa receber qual o símbolo será colocado e em qual posição nos eixos x e y. Após a jogada, a matriz que guarda as posições conterá o o símbolo na posição especificada, mas o método em si não retorna nada.

Apesar de ser um teste bem simples, todas essas decisões de design foram tomadas. Ao pensar no teste sem ter o código real escrito temos a oportunidade de visualizar como esse método será utilizado, ao invés de como ele vai funcionar.

Poderíamos, por exemplo, fazer com que o método retorne um booleano que indica se a jogada foi realizada ou não. Ou então, ao invés de passar o símbolo como parâmetro, poderíamos criar dois métodos, um que faz a jogada para o ‘x’ outro para ‘o’. Poderíamos criar um classe Jogador que armazenaria o símbolo.

O ponto principal é, ao escrever o teste antes pensamos em como vamos utilizar o código e não em como vamos fazê-lo. Então ao fazer TDD evite pensar em o que você faria o código e sim em como seria a melhor maneira de utilizá-lo!

Nos próximos posts vamos criar mais testes para nosso projeto de jogo da velha e usá-lo para refletir sobre o design do código. Fique ligado!

Compartilhe os posts da série Aprendendo TDD! com seus amigos e colegas que também estão buscando aprender mais sobre essa técnica. E acompanhe o blog pois toda semana tem post novo! Dúvidas, sugestões e comentário podem ser feitas aqui no blog ou no twitter em @marcosbrizeno.

Aprendendo TDD! Escrevendo testes unitários

Você provavelmente já jogou o jogo da velha antes, então deve conhecer bem as regras e condições de vitórias. Então vamos usar esse conhecimento para exercitar a escrita de testes!

Testando entrada e saída

Vamos começar com um exemplo simples, queremos testar a entrada e saída do quadro do jogo da velha. Para isso crie um diretório para nosso projetinho e dentro dele adicione uma pasta lib e outra pasta test.

Vamos começar criando o quadro, ele vai receber o caractere do jogador e a posição x e y no qual a jogada foi feita. Para isso coloque o código a seguir dentro do arquivo lib/quadro.rb:

class Quadro
  attr_reader :posicoes

  def initialize()
    @posicoes = []
  end
def executar_jogada(caractere, posicao_x, posicao_y)
    @posicoes[posicao_x] ||= []
    @posicoes[posicao_x][posicao_y] = caractere
  end
end

Para testar esse método, podemos criar uma nova instância de um quadro, chamar o método para fazer a jogada e ao fim verificar se a posição passada foi preenchida com o caractere passado.

Crie os seguintes testes no arquivo test/quadro_test.rb:

require 'minitest/autorun'
require 'minitest/spec'
require_relative '../lib/quadro.rb'

describe Quadro do
  it "realiza jogada na posicao especificada" do
    quadro = Quadro.new
    quadro.executar_jogada('x', 1, 1)
    quadro.posicoes[1][1].must_equal 'x'
  end
end

Para executar os nossos testes, vá para a raiz do diretório e execute:


ruby test/quadro_test.rb

O que tem em um teste

Apesar de simples, o exemplo acima demonstra muito bem o esqueleto que todo teste segue: 1) preparar os dados 2) executar a ação e 3) verificar os resultados.

Ao preparar os dados buscamos garantir tudo o que é necessário para o teste ser executado, sem depender de mais nada. Aqui vamos instanciar novos objetos, configurar dublês (mocks) e qualquer outra informação para exercitar o código.

Na etapa de ação vamos executar o código sob testes, utilizando o mínimo de código possível. É importante que a execução seja apenas uma linha pois queremos que os testes sejam focados em apenas uma ação, assim ao falharem sabemos o motivo.

Na última parte, queremos garantir que os resultados esperados com a ação foram obtidos. Além disso, caso o teste não passe, também temos que deixar claro o que causou a falha.

O que testar?

No exemplo anterior fizemos o conhecido “caminho feliz” que é um cenário onde não consideramos erros ou problemas que possam acontecer. O aconteceria caso o jogador escolha uma posição fora do quadro?

Agora que sabemos como o código funciona podemos escrever o teste primeiro, garantindo que uma exceção será lançada ao fazer uma jogada fora do quadro.

describe Quadro do
  it "lanca excecao se a jogada for numa posicao invalida" do
    quadro = Quadro.new
    assert_raises JogadaInvalidaException do
      quadro.executar_jogada('x', 3, 3)
    end
  end
end

Agora podemos implementar o código que fará com que esse teste passe. Basta validar a posição x e y passadas como entrada e lançar a exceção caso ela seja inválida.

class Quadro
  attr_reader :posicoes

  def initialize()
    @posicoes = []
  end
def executar_jogada(caractere, posicao_x, posicao_y)
    raise JogadaInvalidaException if posicao_x >= 3 || posicao_y >= 3
@posicoes[posicao_x] ||= [] @posicoes[posicao_x][posicao_y] = caractere
end
end

Com isso garantimos não só que o nosso código funciona como esperado, mas também que outros desenvolvedores tenham uma especificação detalhada de como ele funciona. Quem lê os testes sabe que deve tratar a exceção e quando ela será lançada.

Agora, pense em quais outros cenários podemos cobrir. Consegue ver outros cenários de jogadas inválidas? Tente escrever os testes primeiro e depois a implementação.

Nos próximos posts vamos abordar alguns aspectos mais avançados de testes e continuar construindo nosso jogo da velha guiando o desenvolvimento através de testes.

Se você está gostando da série Aprendendo TDD! compartilhe com amigos e colegas que também querem aprender a utilizar esta técnica. Tem alguma dúvida, sugestão ou comentário? Use o espaço no blog ou me procure no twitter em @marcosbrizeno.

Aprendendo TDD! Pra que testes?

“Nossa, o condicional desse if está muito grande e confuso…”, você pensa ao tentar entender a lógica de processamento de pagamento da aplicação. Até pensa em refatorar e extrair a lógica pra um método, mas ai lembra que essa parte é a que valida o pagamento. Melhor deixar quieto e não mexer no que funciona né?

Os benefícios de ter uma boa cobertura de testes, seja fazendo TDD ou o outro TDD (Teste Depois do Desenvolvimento) são muito bons. A confiança de validar o seu trabalho executando apenas um comando são muito grandes. Nós temos a sorte de poder contar com esta técnica na nossa profissão, então vamos usá-la?

TDD está vivo ou morto?

Algum tempo atrás tentaram matar o TDD, mas acho que ele sobreviveu no final das contas. E então, vale a pena fazer TDD? Eu acredito que sim e faço todo dia.

Fazer TDD é difícil e exige uma boa quantidade de prática e treino. Das primeiras vezes que tentei fazer TDD eu simplesmente não conseguia visualizar os testes e saber o que escrever. Então eu decidi focar em fazer testes, mesmo que escrevendo a lógica primeiro, até me acostumar com a estrutura de testes.

Hoje em dia eu consigo sentir que, ao pensar primeiro nos testes e em como eu gostaria de usar o código que vou escrever antes de escrevê-lo, eu consigo visualizar melhor o design e separar as responsabilidades. Mas isso levou um bom tempo, para quem ainda não tem o costume de escrever testes, talvez já começar com TDD seja um esforço muito grande que acabe atrapalhando os benefícios da técnica.

Então, antes de se preocupar em fazer os testes antes, foque em escrever testes e ouvir o feedback que eles te dão.

O que os testes te dizem

Testes são a melhor maneira de documentar seu código, pois eles mudam conforme a lógica. Uma API que possui testes validando o seu contrato, permite que qualquer pessoa veja os testes e entenda quais são os dados que precisam ser enviados.

Uma documentação estática, apesar de importante, exige um esforço extra para os desenvolvedores, não só de escrever, mas também de lembrar de atualizar a cada mudança no código. Então manter os testes legíveis é tão importante quanto manter o código de produção legível.

Ter testes diminui muito a carga cognitiva que precisamos carregar ao desenvolver, pois eles te dão feedback na hora sobre o que você fez. Se você esqueceu de alguma coisa, ou inverteu a lógica do if sem querer, os testes estão lá pra te salvar.

Além disso, o benefício mais óbvio dos testes é aumentar a confiança da equipe ao aplicar mudanças e fazer melhorias no código. Ao corrigir problemas e bugs no código é importante garantir que eles não voltem, adicionando mais testes.

Esse foi o primeiro post da série Aprendendo TDD! Fique ligado pois toda semana terá uma publicação nova com dicas para quem quer começar a escrever testes e aplicar TDD.

 

Entrega Contínua, Testes, Inovação, Agilidade e muito mais no ThoughtWorks Antologia Brasil

ThoughtWorks Antologia BrasilJá pensou em um livro onde você pode aprender com as experiências de várias outras pessoas? Pessoas que passaram por situações parecidas com as suas ou situações completamente diferentes? Essa é a ideia do livro ThoughtWorks Antologia Brasil!

O livro contém um pouco da experiência dos consultores da ThoughtWorks Brasil, que já tem seus 5 anos de existência, e mostra situações desde práticas para Inceptions, projetos Open Source que mudaram o mundo, produtividade em times Ágeis, até Entrega Contínua, técnicas para Testes Automatizados!

Eu tive a felicidade de contribuir para esse livro, junto com outros grandes nomes da comunidade Brasileira, e vou falar um pouco sobre os três capítulos que escrevi lá, pra dar um gostinho do que tem de bom por lá 🙂

Estudo de caso: automação como primeiro passo para entrega contínua

Nesse capítulo eu compartilho uma parte da história de uma das equipes que trabalhei onde existia um processo de deploy 100% manual, onde apenas um grupo de pessoas tinha experiência suficiente para fazer e que sempre precisa ser corrigido em produção.

Com alguns meses de trabalho focado na melhoria do processo, passamos para um processo completamente automatizado, onde apenas um script executava todas as tarefas e ainda era capaz de se recuperar de erros!

Melhore seus testes

Testes automatizados são uma boa parte do nosso trabalho como desenvolvedores. Apesar disso, muitas vezes eles não recebem a atenção que deveriam, o que acaba por tornar doloroso algo que deveria ser o alívio no dia-a-dia da equipe.

Este capítulo é divido em quatro sessões que exploram: boas práticas ao escrever testes; padrões aplicados a testes; o que fazer e como evitar testes instáveis; e por fim os vários níveis de testes e como utilizá-los ao seu favor.

Entendendo e utilizando dublês de teste

Os dublês de testes, ou mocks como são mais conhecidos, são uma ferramenta muito boa para escrever testes nos níveis corretos e melhorar sua eficiência. No entanto eles possuem aspectos únicos que não são bem conhecidos por todos, o que acaba por ferir sua suíte de testes.

Nesse capítulo são descritos em mais detalhes os cinco tipos de dublês mais conhecidos: stub, fake, dummy, mock e spy. Outra discussão feita é sobre como dublês ajudam ao fazer TDD e porque você deveria utilizá-los!

Se interessou pelos assuntos? Tem muito mais no livro, isso aqui é só uma pequena amostra! Se você já leu, comente aqui o que achou. Se não leu ainda, não perde tempo e confere na página da Casa do Código o sumário do livro pra ter um gostinho do que você vai encontrar!