Property-Based Testing: entrando no mundo das propriedades
Quando um cálculo inocente vira dor de cabeça
Tempos atrás trabalhei em um sistema financeiro que precisava calcular o rateio de categorias. O cálculo envolvia tanto valores de competência quanto de caixa – duas visões distintas, mas fundamentais.
Fizemos vários testes: cobrimos cenários esperados, aqueles que todo mundo imaginava que poderiam dar errado. Tudo parecia em ordem. Até que em produção surgiram combinações de valores que ninguém tinha previsto. Os resultados saíam incorretos, relatórios ficavam inconsistentes e a pressão foi imediata: correria para corrigir em produção, mais erros, atrasos acumulados e desgaste com os usuários.
O mais curioso é que os bugs não estavam escondidos em alguma lógica obscura. Eles apareciam justamente em edge cases que simplesmente não foram testados. Situações improváveis mas possíveis como acontece em software o improvável sempre dá um jeito de aparecer.
Foi nessa experiência que percebi: testar apenas casos exemplares nos dá uma falsa sensação de segurança. E foi assim que comecei a enxergar valor em outra abordagem – o property-based testing.
Property-Based Testing
Quando falamos em testar software é comum pensar em casos de uso específicos: “com este input, espero este output”. Essa abordagem funciona, mas às vezes nos dá uma sensação exagerada de segurança. Property-based testing propõe um olhar diferente: em vez de verificar exemplos isolados, vamos identificar regras gerais que o sistema sempre deve respeitar, independentemente dos dados de entrada.
Quais são as propriedades invariantes que sempre devem se manter verdadeiras, não importa o input
Por exemplo:
“O saldo de uma conta nunca pode ser negativo.”
“Um relatório consolidado não pode perder transações no processo.”
“Se eu ordenar uma lista e depois inverter a ordem, devo conseguir recuperar os elementos originais.”
Essas propriedades não descrevem cenários, descrevem leis e é aqui que PBT mostra seu valor: ele gera automaticamente milhares de casos aleatórios para tentar quebrar essas leis. Se alguma falhar, descobrimos uma brecha antes que ela chegue em produção.
Essa mudança de foco ajuda a aumentar a confiança em sistemas complexos e, em experiências relatadas por empresas que adotaram PBT, como Amazon e Volvo, revelou benefícios significativos ao descobrir bugs fora do alcance dos testes tradicionais.
Descobrindo boas propriedades
Uma das dúvidas mais frequentes é: “como encontro essas propriedades?”. Não existe fórmula mágica, mas alguns padrões ajudam a guiar o processo, Comece entrevistando negócio, suporte e finanças. Busque frases que soam como lei:
“Saldo de conta não pode ficar negativo.”
“Não existe fatura paga duas vezes.”
“Pedido não pode ser entregue antes de emitida a nota.”
“Conversão de moedas deve conservar valor até a precisão acordada.”
Transforme cada frase em propriedade usando um padrão simples:
Invariante: “Para todo X, Y… sempre vale Z.”
Limite/monotonicidade: “Se A aumenta, B não pode diminuir.”
Conservação: “Total antes == total depois (considerando taxas/ajustes).”
Reversibilidade/round-trip: “encode(decode(x)) == x (dentro de tolerância).”
Idempotência: “Aplicar a mesma operação N vezes == 1 vez.”
Comutatividade/associatividade: “op(a,b) == op(b,a)” / “op(op(a,b),c) == op(a,op(b,c))”.
Dica: invariantes “chatas” são ouro. As falhas caras nascem quase sempre do óbvio esquecido.
Esses padrões não esgotam as possibilidades, mas fornecem um ponto de partida útil. Outra fonte rica são incidentes passados e contratos de negócio: falhas anteriores e SLAs costumam revelar invariantes implícitas (“saldo nunca negativo”, “transações não duplicadas”). Transformá-las em propriedades evita a repetição desses erros.
psc: futuramente farei um post exclusivo de como ser um caçador de problema xD
Refinando o que realmente importa
Uma vez identificada a propriedade, é hora de refiná-la. Testes baseados em propriedades pedem que você defina domínios de entrada (faixa de valores possíveis), estado inicial e observáveis (o que será medido para validar a propriedade).
No exemplo de testar a função max em uma lista, em vez de enumerar dezenas de casos, você pode dizer: “para qualquer lista, max(list) == last(sort(list))” e deixar o framework gerar dados aleatórios. Se o teste falhar, o mecanismo de shrinking reduz o contraexemplo para um caso mínimo, o que facilita ajustar a propriedade ou o código.
Sugestões práticas:
Varie entradas realistas e maliciosas: valores comuns, extremos e inválidos.
Considere correlações: respeite dependências internas, como data de entrega ≥ data de emissão.
Defina tolerâncias: margens de erro aceitáveis em cálculos numéricos.
Trate exceções como parte do teste: entradas inválidas devem gerar erros claros e sem efeitos colaterais.
Quando abrir ou fechar a caixa
O PBT pode funcionar como teste de caixa preta, em que você não olha para o código interno: apenas fornece entradas e observa as saídas. Essa abordagem tem a vantagem de exercitar o sistema de ponta a ponta, avaliando interface, servidor, banco de dados e integrações.
Mas há momentos em que abrir a “caixa” é útil, como para:
Instrumentar contadores internos: medir valores escondidos (como número de retries ou erros) para garantir que não ultrapassem limites esperados.
Verificar invariantes estruturais em estruturas de dados: checar se propriedades internas (como árvore balanceada ou índices únicos) continuam válidas após operações.
Modelar sequências de estados em fluxos complexos: gerar passos aleatórios em processos (ex.: pedido > pagamento > estorno) e validar que cada transição respeita as regras.
Em muitos casos, combinar abordagens – caixa preta para APIs públicas e caixa branca para estruturas internas – oferece o melhor equilíbrio entre realismo e cobertura.
Pontos de atenção e limites
Apesar de poderoso, o PBT traz alguns desafios:
Complexidade inicial: escrever boas propriedades e geradores demanda esforço.
Sobrecarga de configuração: escolher ferramentas e definir propriedades leva tempo.
Distribuição de dados: geradores mal calibrados podem deixar áreas cegas.
Interpretação de falhas: entender contraexemplos pode exigir investigação cuidadosa.
Flakiness: relógio, timezones e dependências externas podem gerar instabilidade.
Integração com outras técnicas: PBT complementa, não substitui, testes unitários ou de integração.
Adesão no mundo real
Voltando ao caso do cálculo de rateio de categorias, o property-based testing poderia ter sido um grande aliado. O processo partia de um pedido de venda, com valores de frete, impostos e descontos, que depois eram distribuídos entre categorias específicas. A regra de ouro era simples:
a soma dos valores de competência e de caixa deveria ser exatamente igual à soma dos valores rateados.
Com testes tradicionais cobrimos alguns cenários previsíveis, mas com PBT poderíamos ter declarado essa propriedade como lei, deixando o framework gerar automaticamente milhares de combinações de pedidos: com ou sem frete, descontos negativos, impostos fora do padrão, rateios ausentes ou múltiplos por centro de custo (totais ou parciais).
A cada execução, a verificação seria sempre a mesma: a soma dos valores rateados deve bater com os totais do pedido. Se alguma entrada quebrasse essa regra, o teste encontraria o contraexemplo e ainda o reduziria ao caso mais simples para facilitar a investigação.
Em outras palavras, enquanto nós testávamos exemplos “óbvios”, o PBT teria explorado as possibilidades “escondidas” – justamente aquelas que acabaram estourando em produção.
Empresas que lidam com sistemas críticos têm usado PBT há anos em componentes de alta complexidade. Nessas situações, propriedades atuam também como documentação viva, ajudando novos desenvolvedores a compreender o que é essencial no sistema.
Arquitetura, domínio e estratégia
Em um nível mais profundo, propriedades não são apenas aspectos técnicos. Elas revelam o que é essencial no domínio de negócio.
Quando uma fintech afirma que “nenhuma transação pode ser contabilizada duas vezes”, está expressando uma regra inegociável de sobrevivência. Quando um ERP de logística define que “nenhuma entrega pode ser registrada com data anterior à emissão da nota fiscal”, está lidando com a base legal do negócio.
Essas invariantes não são detalhes de código: são decisões arquiteturais que sustentam a confiabilidade do produto. Ignorá-las – ou testá-las apenas com exemplos ocasionais – é abrir espaço para falhas estratégicas.
O property-based testing, nesse sentido, é mais do que uma técnica de QA. É uma forma de alinhar arquitetura de software e lógica essencial do negócio.
O ângulo da confiabilidade
Grandes falhas em sistemas críticos raramente nascem de cenários complexos. Quase sempre, são quebras de propriedades simples:
um saldo que ficou negativo,
uma duplicata que passou batido,
um campo que aceitou valores inconsistentes.
Essas falhas custam caro não apenas em retrabalho, mas em perda de confiança. E confiança, em software corporativo, é capital estratégico.
Ao aplicar PBT, ampliamos o alcance da validação. Em vez de dezenas de casos escolhidos a dedo, temos milhares de combinações testadas de forma automática. Não se trata de eliminar todo risco, mas de aumentar a confiança estatística de que nossas invariantes estão protegidas.
Conclusão
Property-based testing nos convida a refletir: quais verdades devem permanecer imutáveis no nosso sistema?. Não se trata de abandonar os testes que já fazemos, mas de acrescentar uma lente diferente. Essa lente ajuda a expor e proteger as leis invisíveis que regem o negócio, aumentando a confiança no software.
Vale a pena começar pequeno: escolha uma propriedade simples, implemente, e veja o que os testes revelam. A experiência tende a ser reveladora e, com o tempo, gratificante.
Para continuar a conversa…
E você, já passou por situações em que um detalhe não testado acabou virando um problema em produção? Como imagina que o property-based testing poderia ajudar no seu contexto?
Deixe seu comentário abaixo – vou gostar de ouvir suas experiências e trocar ideias.


