PBT e o problema da transferência de R$ 17,99

Mário Melo
facta.works
Published in
5 min readMar 28, 2022

--

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:

Teste convencional para transferências bancárias escrito em Elixir

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:

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).

O teste falha pois o saldo restante foi de R$2,02 e não R$ 2,01

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?

Neste caso, o problema apareceu apenas ao tentar transferir R$ 6,05

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.

Teste baseado em propriedades, utilizando a biblioteca Propcheck

Agora, ao rodar os testes descobrimos que existem outros valores que também acabam por gerar o erro:

Opa! Se eu tiver R$0,98 e tentar transferir R$0,71 o problema também acontece!

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

--

--

Mário Melo
facta.works

An agilist addicted to new technologies that sometimes needs to take a break and beat some goombas