Há algumas semanas uma pessoa descobriu que seu banco não permitia que ela fizesse uma transferência bancária de um valor bastante específico: R$17,99. O que acontecia? O banco automaticamente convertia o valor transferido para R$17,98.
O mais curioso é que transferências de outros valores funcionavam perfeitamente. Bem, quase todos. As pessoas começaram a descobrir outros casos específicos onde o problema acontecia: R$ 32,23 ou R$ 155,17.
A idéia desse post não é debater a causa do problema, mas sim mostrar uma técnica que pode ajudar equipes a detectar problemas parecidos.
O texto inclui algum código em Elixir, mas também inclui ilustrações para explicar cada conceito. O objetivo é tornar este conteúdo útil tanto para pessoas técnicas quanto para pessoas não-técnicas.
Testes unitários ajudariam? Bem… não exatamente.
Não me leve a mal: eu sou fã de testes unitários e (quase) sempre pratico TDD.
Mas um teste convencional para uma função de transferência de dinheiro provavelmente seria algo parecido com o exemplo abaixo:
<a href="https://medium.com/media/906d7d4769e44ed98fe93585ffe5d09b/href">https://medium.com/media/906d7d4769e44ed98fe93585ffe5d09b/href</a>
Não fica muito específico?
Repare que o teste onde a transferência é possível foca em apenas um caso: uma transferência de R$ 25,00. E nesse caso, os testes são um sucesso:
Mas podemos escrever um teste para o caso dos R$17,99, certo?
Claro! Depois de saber da existência do erro poderíamos incluir um teste adicional para garantir que transferências bancárias de R$ 17,99 também podem ser efetuadas:
<a href="https://medium.com/media/85208fb633264ea7ce736ee89fd67fb0/href">https://medium.com/media/85208fb633264ea7ce736ee89fd67fb0/href</a>
Aqui podemos colocar o famoso ciclo do TDD (Test Driven Development, ou desenvolvimento orientado a testes) em ação: Red, Green, Refactor.
Este teste falha (RED), e então podemos corrigir o problema até que o teste passe (GREEN). E agora, tendo o teste como uma rede de segurança podemos deixar alterar o código para torná-lo mais legível sem medo de quebrar nada (REFACTOR).
Pronto! Agora quando fizermos este teste passar vamos garantir que as transferências de R$17,99 funcionarão perfeitamente. Mas algumas perguntas ficam no ar:
A transferência de R$ 32,23 vai funcionar?
E se existirem outros valores que geram o mesmo erro?
E se existirem valores que geram outro erro diferente?
Como podemos evitar que problemas como esse cheguem aos nossos clientes?
E é aí que entra em cena o PBT — Property Based Testing, ou Testes Baseados em Propriedades.
PBT: limpando onde os testes convencionais não alcançam
É bem simples de escrever o teste dos R$17,99 depois que sabemos da existência do erro.
O que acontece é que nós escrevemos os testes com casos que já temos em mente, de maneira bastante linear e específica. Mas o teste é tão específico que testa apenas um caso. E se o problema continuar acontecendo com transferências de outros valores?
Mas… como poderíamos identificar valores problemáticos antes mesmo de saber do problema? É aí que entram os testes baseados em propriedade.
Trabalhar com propriedades significa não pensar em casos específicos, e sim nas características de cada variável envolvida no teste. Neste caso, podemos dizer que:
O saldo inicial de quem vai transferir o dinheiro pode ser qualquer valor maior que zero.
O valor da transferência é maior que zero e menor ou igual ao saldo disponível.
Para saber se a transferência foi feita com os valores corretos basta a gente "tirar a prova", como fazíamos no ensino fundamental. :)
Valor transferido + Saldo remanescente = Saldo antes da transferência
Quando definimos as regras desta maneira, as ferramentas de testes automatizados são capazes de validar diversos casos diferentes. E muitas vezes esses "casos diferentes" acabam incluindo situações que sequer imaginamos, e por isso jamais escreveríamos um teste convencional sobre elas.
<a href="https://medium.com/media/58003270e671641b73f493bbca69083b/href">https://medium.com/media/58003270e671641b73f493bbca69083b/href</a>
Agora, ao rodar os testes descobrimos que existem outros valores que também acabam por gerar o erro:
Ah! Repare que a biblioteca de testes diz: Counter example stored.
As bibliotecas de PBT costumam armazenar os valores que geraram erros e usá-los novamente até que o problema seja corrigido. Ou seja, quando o teste passar é porque o problema foi resolvido, e não porque a biblioteca selecionou apenas valores para os quais o problema não acontece.
Tá, mas o que isso tem a ver com agilidade?
Bem, agilidade é a capacidade de mudar e gerar resultados rapidamente.
É como se você estivesse em um carro de corrida em uma pista sinuosa: para fazer curvas rapidamente e sem perder muita velocidade você vai precisar de um veículo sólido e bem construído. Sem isso, você se limita a duas opções:
Fazer a curva beeeeem devagarzinho para garantir que o veículo não quebre
Fazer a curva rapidamente e desmantelar seu veículo
Exemplo:
Imagine que o banco decidisse permitir contas em Bitcoins. Teriam agora que trabalhar com uma moeda que tem bem mais do que duas casas decimais.
Agora imagine que o banco pode ter uma aplicação que:
a) Não tem nenhum teste automatizado
b) Tem alguns testes automatizados
c) Tem testes automatizados convencionais e alguns utilizando PBT
Em qual cenário o banco conseguiria ter um produto funcional em produção mais rapidamente?
Resumindo: Não existe agilidade sem excelência técnica.
Código Fonte e considerações finais
Eu escrevi o código para gerar o erro de forma proposital e assim ilustrar como uma determinada técnica poderia ajudar a resolver um problema.
Caso você tenha curiosidade, o código utilizado neste post está disponível em: https://github.com/mariomelo/post_pbt
PBT e o problema da transferência de R$ 17,99 was originally published in facta.works on Medium, where people are continuing the conversation by highlighting and responding to this story.