Developer Experience

Já parou para pensar nas ferramentas que você, desenvolvedor, usa no dia-a-dia? Do editor de texto/IDE até como o software é implantado em produção, o que você utiliza precisam oferecer uma boa experiência, assim como um novo modelo de celular.

A evolução da Experiência de Uso (User eXperience – UX) nos proporciona maneiras cada vez melhores de interagir com nossos dispositivos, software e até nós mesmos. De maneira semelhante a evolução da Experiência de Desenvolvimento muda a maneira como o software é desenvolvido e como interagimos em equipe.

A revolução da Nuvem

Um grande benefício, poucas vezes reconhecido, de utilizar um serviço como Amazon ou Heroku é poder criá-las sem muita burocracia. Um git push e sua aplicação está rodando no Heroku, sem dependências com um time de infraestrutura.

De maneira semelhante, a popularização de contêineres com o Docker permite que você consiga rodar na sua máquina uns dois servidores Phoenix com um NGINX para balancear a carga, um Mongo para guardar dados, Memcached para fazer caching e um Nagios para monitorar tudo isso. Tá, talvez não tudo isso, mas dá pra endenter a ideia 🙂

O benefício de poder experimentar com tecnologias diferentes e colocá-las em produção sem muitas adaptações na infraestrutura aumenta muito a experiência de desenvolvimento.

O tempo para Hello World

O Hello World é a maneira mais fácil de experimentar uma ferramenta nova, não é nada complexo complexa e dá uma boa visão sobre como configurar um ambiente. O framework Rails ficou bem conhecido pela sua capacidade de permitir experimentação muito rápido, basta rodar alguns simples comandos e você tem uma estrutura completa, com views e banco de dados 100% funcionais.

Hoje em dia praticamente qualquer framework oferece alguma maneira de criar uma estrutura básica, com diretórios e configurações básicas. A linguagem Crystal (https://crystal-lang.org) já possui um comando que inicializa os diretórios, cria arquivos básicos, testes e inclusive configurações de um repositório Git – isso não é uma biblioteca, o executável da linguagem oferece isso!

Outro bom exemplo é o http://tryruby.org/ que permite aprender sobre a linguagem Ruby direto no navegador, sem instalar nada. Essa ideia levou o primeiro contato com a linguagem para outro nível de facilidade e hoje em dia várias linguagens/ferramentas possuem algo do tipo, como o tutorial de Go https://tour.golang.org/.

Esses são só alguns exemplos de como a preocupação em melhorar a vida dos desenvolvedores leva a criação de novas ferramentas que simplificam e facilitam seu uso. Nos próximos posts vamos explorar melhor o impacto dessas facilidades nos times de software, então acompanhe o blog e fique ligado 🙂

Anúncios

3 dicas para utilizar Padrões de Projeto

Aplicar um Padrão de Projeto pode ser um processo não muito simples, então siga essas dicas para ter mais confiança e entendimento antes de começar. Se quiser ir mais afundo no tema e entender como Padrões de Projeto podem ajudar, leia o livro Refatorando com Padrões de Projeto.

  1. Identifique a oportunidade de melhoria: Aplicar um Padrão de Projeto aumenta a complexidade do código, portanto é preciso ter certeza que vale a pena aplicá-lo. Comece pensando no que o código ganharia se o padrão for aplicado, pode ser tanto um code smell que será resolvido quanto uma alteração futura que será facilitada.
  2. Entenda o contexto do código: Cada Padrão de Projeto possui um contexto onde ele faz sentido e de fato resolve o problema, apesar de aumentar a complexidade. Aplicar um padrão fora de contexto vai causar tanto dificultar sua implementação quanto dificultar o entendimento dos outros. O coitado do Builder que o diga.
  3. Aplique pequenas mudanças nos testes e no código: No post anterior falei sobre a diferença entre refatorar e mudar o código, se quiser aplicar um padrão refatore! Essas mudanças em pequenos passos, além de te dar mais segurança, também servem para te dar um entendimento melhor do design que você está propondo. Pode ser que no meio da refatoração você descubra que apenas modificar um pouco o design resolve o problema, sem adicionar toda a complexidade dos padrões.

Lembre-se sempre que Padrões de Projeto adicionam complexidade ao código, então é preciso entender se vale a pena aplicá-los ou não. Se decidir por aplicá-los, siga sempre refatorando o código, evite fazer grandes mudanças no código de qualquer jeito, seja um profissional disciplinado!

Refatoração não é só mudar o código!

Você provavelmente já ouviu alguém falando em refatoração, mas será que a pessoa realmente estava falando de refatoração? Qual a diferença entre refatorar e apenas modificar o código? Se quiser ir mais afundo no tema e entender como Padrões de Projeto podem ajudar, leia o livro Refatorando com Padrões de Projeto.

É importante entender a diferença, apesar de parecer algo mais conceitual, pois a mentalidade ao refatorar o código é diferente de quando implementamos uma funcionalidade nova ou estamos corrigindo um problema.

Ao refatorar buscamos maneiras de (1) melhorar o design existente, (2) aplicando mudanças em pequenos passos e (3) evitando deixar o sistema quebrado. Vamos falar sobre essas três partes em mais detalhes.

Ao buscar maneiras de melhorar o design existente devemos pensar em novas soluções que podem melhorar o código (um pensamento bem diferente de quando estamos corrigindo um bug).

A segunda parte é muito importante pois ao aplicar mudanças em pequenos passos garantimos a consistência e ritmo de desenvolvimento. Quanto menos mudanças fizermos mais fácil é evitar problemas.

E a terceira parte é bem ligada a segunda pois, ao fazer mudanças pequenas, podemos executar todos os testes e validar o que está sendo feito. Se o tempo entre ter testes verdes for muito grande, é um sinal de que talvez as mudanças devam ser menores.

As técnicas de refatoração descritas por Martin Fowler seguem esse modelo pois descrevem um passo-a-passo onde aplicamos uma mudança pequena e executamos os testes até alcançar o objetivo final da refatoração.

Se você quer refatorar o código o primeiro passo deve ser identificar o code smell e qual ação quer tomar.

Digamos que você encontrou um método que tem muitos argumentos e quer pensar numa maneira de melhorá-lo. Você pode criar um novo objeto para armazenar esses dados e passar apenas esse objeto, outra opção seria quebrar o método e dividir a lógica e parâmetros, ou ainda mover alguns desses parâmetros para um construtor.

Existem várias opções ao refatorar um código então é muito importante entender o contexto do código e decidir o caminho antes de começar as modificações.

Em seguida vamos aplicar a refatoração, sempre lembrando de fazer mudanças pequenas e garantir que os testes passem. Se no meio de uma refatoração você encontrar uma oportunidade de corrigir um problema, anote em algum lugar e continue. Lembre-se que queremos diminuir a quantidade de mudanças.

Uma vez que a refatoração foi aplicada podemos criar um commit pois deixamos o sistema com todos os testes passando. Agora podemos voltar ao modo de corrigir bugs ou implementar funcionalidades.

Nos próximos posts vamos ver como Padrões de Projeto podem nos ajudar na hora de decidir o caminho das refatorações. Até lá.

Aprendendo TDD! Mais sobre dublês de teste

No post anterior vimos como os dublês de testes, mais conhecidos por mocks, podem nos ajudar a testar alguns cenários “impossíveis”. Vamos então entender como deixar nossos testes mais confiáveis, isolados e rápidos com outros tipos de dublês!

O artigo The Little Mocker, de Uncle Bob, é leitura recomendadíssima para aprender mais sobre os diferentes tipos de dublês.

Como exemplo vamos imaginar que queremos escrever testes que dependem da seguinte classe que é responsável por autorizar o pagamento de um cartão de crédito:

class ServicoValidacaoPagamento
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    # Lógica complexa para autorizar um pagamento
  end
end

Stub

Utilizamos um Stub quando não precisamos nos preocupar com a execução de um método, apenas com o resultado que ele retorna. Para criar um stub para o código anterior, poderíamos fazer algo como:

class ServicoValidacaoPagamentoStub
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    true
  end
end

Sempre que utilizarmos ServicoValidacaoPagamentoStub todos os pagamentos serão autorizados, assim para os nossos testes não precisamos nos preocupar com a sua lógica para retornar o que queremos.

Para testar que um pedido é finalizado quando o pagamento é validado poderíamos utilizar o stub assim:

it "marca pedido como finalizado quando pagamento eh aprovado" do
  pedido = Pedido.new(ServicoValidacaoPagamentoStub.new)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  pedido.estado.must_equal EstadoPedidos::FINALIZADO
end

Spy

Um Spy é utilizado quando queremos validar que métodos foram chamados, quantas vezes, com quais argumentos e quaisquer outras informações que nos interesse.

Supondo que agora queremos validar que, ao finalizar o pedido, estamos utilizando os dados corretos do cartão de crédito para validar o pagamento. Esse é um spy que poderíamos utilizar:

class ServicoValidacaoPagamentoSpy
  attr_reader :numero_cartao, :mes_expiracao, :ano_expiracao, :autorizacao_executada
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    @autorizaca_executada = true
    @numero_cartao = numero_cartao
    @mes_expiracao = mes_expiracao
    @ano_expiracao = ano_expiracao
    true
  end
end

Com o Spy, além de forçar o valor de retorno também estamos guardando informações se o método foi chamado e quais argumentos foram passados. O teste utilizaria o Spy da seguinte forma:

it "chama servico de validacao ao finalizar pagamento" do
  spy = ServicoValidacaoPagamentoSpy.new
  pedido = Pedido.new(spy)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  spy.autorizaca_executada.must_equal true
spy.numero_cartao.must_equal cartao_de_credito.numero
spy.mes_expiracao.must_equal cartao_de_credito.mes_expiracao
spy.ano_expiracao.must_equal cartao_de_credito.ano_expiracao
end

Utilizando um spy quando queremos validar informações relacionadas a como o método foi executado.

Mock

Assim como o Spy, o Mock também valida métodos que foram chamados. A diferença é bem sútil, um Mock sabe o que deve ser testado, ao contrário do Spy que apenas guarda informação da execução.

class ServicoValidacaoPagamentoMock
  attr_reader :autorizacao_executada
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    @autorizaca_executada = true
    true
  end

def verifica_execucao()
autorizacao_executada.true?
end
end 

Um mock possui a lógica de validação dentro dele, assim o teste fica mais simples. No entanto a lógica de verificação pode ser mais complexa do que apenas se o método foi executado, como mostra o exemplo. O teste ficaria assim:

it "chama servico de validacao ao finalizar pagamento" do
  mock = ServicoValidacaoPagamentoMock.new
  pedido = Pedido.new(mock)
  pedido.adicionar_produtos(produtos)
  pedido.forma_de_pagamento(cartao_de_credito)

  pedido.finalizar # vai chamar o serviço para validar o pagamento

  mock.verifica_execucao.must_equal true
end

Stub, Spy ou Mock?

A razão pelo qual Dublês de Testes são conhecidos como mocks é que a diferença entre eles é bem sutil e muitas vezes os frameworks vão empacotar todas essas funcionalidades e chamar de mock.

Na prática utilizamos na maioria das vezes stubs, para apenas substituir o comportamento de um método por seu valor fixo, ou um spy, para avaliar informação relacionadas a chamadas de método. Mas como os autores dos frameworks decidiram chamar tudo de mock ¯_(⌣̯̀⌣́)_/¯

Se quiser ir mais a fundo para entender a diferença entre eles, sugiro o artigo Mocks Aren’t Stubs de Martin Fowler que contém vários exemplos e mais detalhes.

Dummy

Um Dummy é utilizado para substituir um objeto quando você não precisa se importar como ele será utilizado. Geralmente ele é utilizado para melhorar a legibilidade, já que não possuem nenhum comportamento. Esse seria um Dummy para o serviço de autorização de pagamento:

class ServicoValidacaoPagamentoDummy
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
end
end

O Dummy recebe chamadas, mas não faz nada com elas. O principal objetivo é melhorar a legibilidade, já que sempre que criamos um pedido precisamos passar um serviço podemos utilizar o Dummy ao invés de um nil:

it "adicionar produtos ao pedido" do
  pedido = Pedido.new(ServicoValidacaoPagamentoDummy.new)
  pedido.adicionar_produtos(produtos)

pedido.quantidade_produtos.must_equal 1
end

Fake

Semelhante ao Dummy, o Fake é utilizado para substituir objetos no entanto ele possui uma determinada lógica que é será utilizada em diferentes cenários de teste. Por exemplo:

class ServicoValidacaoPagamentoFake
  def autorizar_pagamento(numero_cartao, mes_expiracao, ano_expiracao)
    numero_cartao == "1234-1234-1234-1234"
  end
end 

Assim, podemos utilizar nos testes e simular a falha/sucesso do pagamento sem precisar mudar o dublê.

it "marca pedido como finalizado quando pagamento eh aprovado" do
  pedido = Pedido.new(ServicoValidacaoPagamentoFake.new)
pedido.adicionar_produtos(produtos)
pedido.forma_de_pagamento(cartao_de_credito_valido)
# cartao_de_credito_valido possui o numero esperado pelo Fake

pedido.finalizar # vai chamar o serviço para validar o pagamento

pedido.estado.must_equal EstadoPedidos::FINALIZADO
end

it "marca pedido como pendente quando pagamento nao eh aprovado" do pedido = Pedido.new(ServicoValidacaoPagamentoFake.new)
pedido.adicionar_produtos(produtos)
pedido.forma_de_pagamento(cartao_de_credito_invalido)
# cartao_de_credito_invalido possui outro numero

pedido.finalizar # vai chamar o serviço para validar o pagamento

pedido.estado.must_equal EstadoPedidos::PENDENTE
end

Dublês de Teste na vida real

Na maioria das vezes que utilizei dublês foi através de uma biblioteca que cria esses objetos. Apesar da facilidade que a biblioteca proporciona é importante entender o motivo da utilização do dublê e também saber que escrever seu próprio não é algo tão difícil assim.

Espero que tenha gostado desse post e que consiga utilizar dublês de teste com mais efetividade! Apesar de ser uma excelente ferramenta eles precisam ser utilizados com cuidado e bastante consciência.

Continue acompanhando a série com posts semanais Aprendendo TDD! Compartilhe com amigos e colegas que querem aprender. Se tem alguma sugestão, dúvida ou comentário use o espaço aqui do blog ou me procure no twitter em @marcosbrizeno. Até a próxima!

Aprendendo TDD! Utilizando Dublês de Teste

No posto anterior fizemos o famoso FizzBuzz utilizando os testes como guia e analisando as decisões de design tomadas ao longo do desenvolvimento. No entanto, no mundo real nem tudo consegue ser testado diretamente e são nesses casos que precisamos utilizar os dublês de teste!

O que são dublês de testes?

Precisamos alertar que a conta do usuário vai expirar caso o pagamento do último mês não tenha sido efetuado. Como adicionaríamos testes para o seguinte código?

class Usuario
  attr_reader :ultimo_pagamento
  UM_MES = 60*60*24*30

  def realizar_pagemento()
    @ultimo_pagamento = Time.now
  end

  def deve_expirar_conta()
    @ultimo_pagamento <= Time.now - (UM_MES)
  end
end

Obviamente não conseguimos fazer o pagamento e esperar um mês para verificar se a conta foi expirada, então precisamos utilizar um dublê de teste para fingir a passagem do tempo.

Dublês de testes são mais conhecidos como mocks, mas existem outros tipos. No artigo The Little Mocker, Uncle Bob explica de maneira bem fácil de acompanhar quais são os vários tipos de dublês, incluindo o mock, e quando é o melhor momento para utilizar cada um deles. No próximo post vamos entrar em mais detalhes sobre os diferentes dublês.

Para o código anterior nós vamos utilizar um stub, que permite alterar o comportamento de um método para ignorar sua lógica e retornar um valor específico. O que é exatamente o que precisamos para testar o código anterior, ao invés de Time.now retornar o tempo atual, vamos retornar uma data que permita exercitar o código.

Testes com dublês

A sintaxe para criar stubs e outros dublês muda um pouco de biblioteca para biblioteca, no caso do MiniTest, definimos o método que desejamos substituir o comportamento, o valor que deve ser retornado e o bloco de código onde aquele stub será válido.

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

describe Usuario do
  UM_MES =(60*60*24*30)
  it "expira conta quando pagamento nao foi feito" do
    usuario = Usuario.new
Time.stub(:now, Time.now() - UM_MES) do
      usuario.realizar_pagemento()
    end

usuario.deve_expirar_conta.must_equal true
  end
end

Além do tempo existem outras situações onde não queremos utilizar a implementação real dos métodos como por exemplo, chamadas de serviços externos pela rede, leitura de arquivos locais ou até mesmo chamadas ao banco de dados.

A principal vantagem que eu vejo de utilizar dublês ao fazer TDD é que eu consigo isolar melhor os meus testes e não preciso me preocupar muito com a maneira como outros objetos funcionam. Assim posso focar apenas no design do código que estou desenvolvendo e passar pelo ciclo do TDD mais facilmente. No entanto é preciso tomar cuidado para não utilizá-los de maneira errada!

Quando não utilizar

No artigo Mockar ou não mockar, eis a questão,  Fabio Pereira explora bem os malefícios de utilizar muitos dublês de testes no seu código.

Uma das guerras sagradas no desenvolvimento de software é definir o que é um teste unitário, pois algumas pessoas restrigem ao ponto de utilizar dublês para quaisquer outros objetos, isolando a classe que está sendo testada, outros sugerem que até mesmo chamadas ao banco de dados podem ser feitas em testes unitário.

Independente da sua definição de teste unitário, ao utilizarmos um dublê estamos ignorando toda a lógica implementada naquele método/classe em favor de isolar o teste. Caso essa lógica mude o teste não vai saber e é aí onde mora o perigo.

Devido a maneira como ruby lida com objetos, é possível que o método nem sequer exista mas que ainda assim o teste funcione. O código a seguir utiliza um mock para criar um objeto e simular o seu comportamento, mas a asserção é feita sobre o mock.

it "mata um panda" do
  usuario = MiniTest::Mock.new
  usuario.expect :metodo_inexistente, true

  usuario.metodo_inexistente.must_equal true
end

Utilizar dublês é necessário e tem muitos benefícios, como vimos no exemplo de testes com o tempo. No entanto é preciso ficar atento pois podemos utilizar tantos dublês que no final das contas o teste não testa nada, como nesse exemplo anterior.

No próximo post vamos explorar mais sobre dublês de testes e situações onde podemos utilizá-los para nossos testes.

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! Testes guiando o desenvolvimento!

TDD é uma técnica que requer prática, como já falei nos outros posts da série Aprendendo TDD! Então precisamos praticar! Nesse post vamos pegar um exemplo simple, o Fizz Buzz e desenvolvê-lo utilizando testes como nosso guia e seguindo o fluxo do TDD.

Teste -> Código -> Refatoração

Antes de entrar no exemplo em si, vamos analisar o ciclo do TDD e entender qual a importância de cada fase. Você provavelmente já viu esse ciclo antes em palestras ou apresentações, mas é importante relembrar!

tdd-ciclo-vermelho-verde-refatora

Ciclo do TDD

A primeira etapa é escrever o teste que expressa o que você gostaria que o código fizesse. Como falado no post anterior, evite pensar em como você irá fazer o código e sim em como você gostaria de utilizá-lo.

Nesse ponto os testes obviamente vão falhar, pois não temos o código ainda, mas isso é bom. Se você escreve um teste e ele passa, provavelmente o teste não está explorando o cenário que você quer ou ele ele vai adicionar pouco ou nenhum valor.

Em seguida vamos escrever o código mínimo necessário para que o teste passe. A ideia aqui não é pensar na solução perfeita, mas sim na mais simples. Melhorias no código serão feitas no próximo passo do ciclo, então queremos chegar lá o quanto antes.

Na vida real é fácil perceber que o código mínimo vai precisar ser alterado já no próximo teste, mas ainda assim é importante manter o pensamento na solução mais simples possível.

Por fim vamos pensar em como refatorar o código, dado que temos testes que garantem que não vamos quebrar nada. A cada ciclo do TDD vamos ganhando mais entendimento sobre o problema e adaptando o código de maneira segura.

É importante lembrar não só de refatorar o código mas também os testes. No código buscamos evitar duplicação para facilitar a manutenção, nos testes buscamos melhorar a legibilidade para que pessoas possam entender melhor como nosso código funciona.

Utilizando o ciclo TDD

O Fizz Buzz é um jogo onde precisamos dizer os números mas com uma pequena mudança. Sempre que o número for divisível por 3 dizemos “fizz” ao invés do número, se for divisível por 5 dizemos “buzz” e se for divisível tanto por 3 quanto por 5 dizemos “fizz buzz”.

Então vamos fazer um programa que recebe um número e vai mostrar o que uma pessoa diria no Fizz Buzz até esse número. Para começar vamos fazer o caso mais simples, quando precisamos dizer apenas o número.

Crie uma nova pasta para o código e dentro dela adicione uma pasta lib e outra test. Dentro de test adicione o arquivo fizz_buzz_test.rb e vamos escrever nele nosso primeiro caso de teste:

# test/fizz_buzz_test.rb
require 'minitest/autorun'
require 'minitest/spec'
require_relative '../lib/fizz_buzz.rb'
describe FizzBuzz do
  it "retorna o numero quando nao for divisivel por 3 ou por 5" do
    fizz_buzz = FizzBuzz.new(1)
    contagem = fizz_buzz.gerar_contagem()
    contagem[0].must_equal '1'
  end
end

Lembrando do post anterior, você consegue identificar as decisões de design tomadas nesse código? Pense um pouco antes de continuar.

Ao criarmos uma nova classe FizzBuzz passamos para ela, já no construtor, até qual número queremos contar. Eu tomei essa decisões pois quero que um objeto FizzBuzz seja utilizado apenas uma vez para cada contagem, assim espero diminuir a complexidade dele.

Em seguida temos um método de um objeto FizzBuzz que vai gerar a contagem, retorna um conjunto com as strings relativas a cada número. Essa decisão foi tomada pensando para dar mais liberdade a quem utiliza o nosso FizzBuzz. Retornar um conjunto ao invés de, por exemplo, uma string com a contagem, permite que quem utilize o código manipule a contagem de maneira mais fácil.

Por fim, como retornamos um conjunto, o teste valida que na posição inicial vamos ter a string “1”. A decisão de retornar o conjunto também facilitou a nossa vida para escrever testes, já que não precisamos manipular uma string para verificar que o código funciona como esperado.

Execute o teste acima através do comando ruby test/fizz_buzz_test.rb. O teste deve falhar pois não temos nem a classe FizzBuzz, então se o seu teste falhar por esse motivo estamos no caminho certo 🙂

Agora podemos escrever o código para fazer esse teste passar. Precisamos criar a classe, o construtor e o método que vai gerar a contagem.

class FizzBuzz
  attr_reader :limite_contagem

  def initialize(limite_contagem)
    @limite_contagem = limite_contagem
  end

  def gerar_contagem()
    ['1']
  end
end

Lembre que precisamos escrever o código mais simples possível para chegar logo na fase de refatoração, onde todos os testes estão passando. Então basta que o método gerar_contagem retorne um array contendo a string esperada 🙂

Na vida real não vamos escrever código assim pois sabemos que ele vai precisar mudar logo, mas é importante manter na cabeça a simplicidade da solução. Por hora não temos a necessidade de refatorar nenhum código, então vamos para os próximos casos te teste.

Próximos passos

Qual seria o próximo caso de teste? Vale a pena testar uma contagem até o número 2?

Eu acho que não pois contar até o número 2 não vai expor nenhuma regra diferente. Podemos partir para testar a contagem até o número 3, pois vamos aplicar a regra do “fizz”.

# test/fizz_buzz_test.rb
it "retorna fizz quando o número for 3" do
  fizz_buzz = FizzBuzz.new(3)
  contagem = fizz_buzz.gerar_contagem()

  contagem[2].must_equal 'fizz'
end

Agora podemos implementar o código. Lembrando da ideia de manter a solução simples, podemos fazer simplesmente isso:

def gerar_contagem()
  ['1', '2', 'fizz']
end

Execute os testes novamente e eles devem passar! Agora que temos essa garantia podemos pensar em como escrever esse código melhor, já que criar um array na mão não é solução que vai escalar muito bem.

Podemos refatorar o código para gerar cada número e verificar se o número é divisível por 3 para retornar “fizz”.

def gerar_contagem()
(1..@limite_contagem).map { |numero|
numero % 3 == 0 ? 'fizz' : numero.to_s
}
end

Execute os testes novamente e eles devem continuar passando! É importante lembrar que, enquanto refatoramos o código os testes devem passar a cada mudança. Se você passa muito tempo com os testes falhando é um sintoma de que ou a mudança é muito grande, e deveria ser dividida em passos menores, ou você não entende muito bem o código que precisa ser modificado.

Mais TDD!

Ainda temos duas regras que não foram documentadas nos testes: quando o número for divisível por 5 e quando for tanto por 3 quanto por 5. Você consegue pensar em como quais seriam os próximos testes?

Uma vez que as regras de negócio estão documentadas nos testes, podemos pensar em outros casos que não estão cobertos. Quais casos, além dos dois já falados poderíamos adicionar? O que acontece quando passarmos um número negativo para gerar a contagem? E se nenhum número for passado?

Outro exercício interessante é modificar o design da aplicação e recomeçar o ciclo. Por exemplo, imagine agora que não queremos criar um objeto para cada sequência FizzBuzz. Ao invés disso vamos ter apenas um método de classe que vai gerar a contagem e retornar o mesmo array.

Espero que tenha gostado desse post e que tenha conseguido desenvolver o código utilizando os testes como guia. Tente terminar o exercício e pensar em maneiras mais simples de resolvê-lo, lembrando sempre de deixar os testes passando!

Continue acompanhando a série com posts semanais Aprendendo TDD! Compartilhe com amigos e colegas que querem aprender. Se tem alguma sugestão, dúvida ou comentário use o espaço aqui do blog ou me procure no twitter em @marcosbrizeno. Até a próxima!

 

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.