O que testes-baseados-em-propriedades faz?
Fornece orientação para testes baseados em propriedades em várias linguagens e contratos inteligentes. Use ao escrever testes, revisar código com padrões de serialização/validação/análise, projetar recursos ou quando testes baseados em propriedades fornecerem cobertura mais forte do que testes baseados em exemplos.
O que são testes baseados em propriedades?
Testes baseados em propriedades são uma estratégia onde você define propriedades de alto nível que seu código deve sempre cumprir (invariantes). Em vez de escrever centenas de casos de teste manuais, você descreve uma propriedade e uma ferramenta gera milhares de entradas válidas (e, se possível, inválidas) para verificar automaticamente se a propriedade é violada.
Principais conceitos
- Invariantes: Propriedades que sempre devem ser verdadeiras (ex.:
sortseguido dereverseduas vezes retorna ao estado original). - Entradas geradas aleatoriamente: O framework cria entradas aleatórias para testar o comportamento do sistema.
- Redução de contraexemplos (shrink): Quando um teste falha, o framework tenta gerar o menor exemplo possível que ainda cause a falha, facilitando o debugging.
Quando usar este skill
- Ao escrever testes unitários ou de integração, especialmente para funções de parsing, validação, serialização ou transformações de dados.
- Ao revisar código onde existem muitas combinações possíveis de entrada (por exemplo, uma API de entrada de usuário).
- Ao projetar novos recursos para garantir que as especificações estejam corretas desde o início.
- Ao trabalhar com contratos inteligentes, onde a cobertura abrangente é crítica para segurança.
Linguagens e ferramentas suportadas
| Linguagem | Ferramenta recomendada | Exemplo de uso |
|---|---|---|
| Rust | proptest ou quickcheck | Testar funções de serialização |
| Python | Hypothesis | Validar decodificadores de rede |
| JavaScript | fast-check | Testar parsers de JSON/Markdown |
| Solidity | Foundry (via fuzz testing) | Verificar invariantes de DeFi |
| Java | jqwik | Garantir que objetos ORM sejam válidos |
Como usar
Exemplo 1: Rust com proptest
use proptest::prelude::*;
proptest! {
#[test]
fn reverse_nao_duplica_elementos(v: Vec<i32>) {
let mut reverso = v.clone();
reverso.reverse();
// Propriedade: reverter duas vezes volta ao original
assert_eq!(v, reverso);
}
}
Exemplo 2: Python com Hypothesis
from hypothesis import given, strategies as st
def decode_dados(entrada: bytes) -> dict[str, int]:
# Implementação simples para exemplo
resultado = {}
if len(entrada) % 8 != 0:
raise ValueError("Tamanho inválido")
for i in range(0, len(entrada), 8):
chave = entrada[i:i+4].decode("ascii", errors="ignore")
valor = int.from_bytes(entrada[i+4:i+8], "little")
resultado[chave] = valor
return resultado
@given(st.binary(min_size=0, max_size=64))
def test_decode_nao_lanca_excecao_inesperada(dados: bytes) -> None:
try:
resultado = decode_dados(dados)
for chave, valor in resultado.items():
assert isinstance(chave, str) and isinstance(valor, int)
except ValueError:
pass # Esperado para entradas inválidas
Exemplo 3: Solidity com Foundry Fuzzing
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "forge-std/Test.sol";
contract TestVault {
Vault public vault;
function setUp() public {
vault = new Vault();
}
// Propriedade: totalSupply nunca deve exceder o limite máximo
function testFuzz_totalSupplyLimite(uint256 amount) public {
vm.assume(amount > 0 && amount <= 100 ether);
vault.depositar{value: amount}();
assertLe(vault.totalSupply(), 100 ether);
}
}
Exemplo 4: JavaScript com fast-check
const fc = require("fast-check");
const { parseISO, isValid } = require("date-fns");
function processarDataString(str) {
const data = parseISO(str);
if (!isValid(data)) {
return null;
}
return { ano: data.getFullYear(), mes: data.getMonth() + 1, dia: data.getDate() };
}
fc.assert(
fc.property(fc.isoDateString(), (dataStr) => {
const resultado = processarDataString(dataStr);
// Propriedade: strings ISO válidas sempre produzem resultado não nulo
expect(resultado).not.toBeNull();
expect(resultado.ano).toBeGreaterThan(0);
})
);
Boas práticas
- Comece com invariantes simples: Identifique propriedades que são sempre verdadeiras independentemente da implementação.
- Use shrinking ativado: A maioria dos frameworks já vem com shrinking habilitado; não o desative a menos que tenha um motivo forte.
- Combine com testes baseados em exemplos: Testes baseados em propriedades complementam, não substituem, testes de exemplo para casos limite conhecidos.
- Teste também entradas inválidas: Propriedades como "não lança exceções inesperadas" são poderosas para código defensivo.
- Documente as propriedades bem: Cada propriedade deve vir acompanhada de um comentário explicando por que ela é verdadeira.
Referências
- Documentação oficial do proptest (Rust)
- Hypothesis Quick Start (Python)
- fast-check (JavaScript)
- Foundry Fuzz Testing (Solidity)
- jqwik (Java)
Contribuindo
Contribuições são bem-vindas! Por favor, abra um issue ou pull request se tiver sugestões de melhoria ou exemplos adicionais para outras linguagens.