---
title: "Testes de Propriedade e Fuzzing em Rust: proptest e cargo-fuzz em 2026"
url: "https://rustlang.com.br/blog/rust-proptest-fuzzing-property-based-testing-2026/"
markdown_url: "https://rustlang.com.br/blog/rust-proptest-fuzzing-property-based-testing-2026.MD"
description: "Como aplicar testes baseados em propriedades e fuzzing em Rust com proptest e cargo-fuzz em 2026: strategies, shrinking, seeds reproduzíveis, integração com CI e casos reais em parsers, serialização e lógica de domínio."
date: "2026-06-21"
author: "Equipe Rust Brasil"
---

# Testes de Propriedade e Fuzzing em Rust: proptest e cargo-fuzz em 2026

Como aplicar testes baseados em propriedades e fuzzing em Rust com proptest e cargo-fuzz em 2026: strategies, shrinking, seeds reproduzíveis, integração com CI e casos reais em parsers, serialização e lógica de domínio.


## Por que testes de propriedade mudam a forma de testar em Rust

Testes unitários clássicos respondem a uma pergunta importante: "dada esta entrada, a saída é esta?". São diretos, legíveis e essenciais. Mas eles têm uma limitação estrutural — só cobrem as entradas que você imaginou. Bugs reais costumam morar nas entradas que você não imaginou: uma string Unicode com combining marks, um vetor com elementos repetidos, um timestamp no limite de fuso, um JSON com chave duplicada, um número negativo onde o domínio esperava positivo.

Testes baseados em propriedades (property-based testing) invertem a pergunta. Em vez de dizer "para esta entrada, espere esta saída", você diz "para qualquer entrada válida, esta propriedade deve valer". O framework gera então centenas ou milhares de casos automaticamente, usando estratégias que descrevem o universo de entradas possíveis. Se alguma entrada quebrar a propriedade, o framework faz o **shrinking** — reduz o contraexemplo ao menor caso que ainda reproduz a falha, transformando um vetor caótico de 40 elementos em um de 2.

Em Rust, essa abordagem é particularmente poderosa porque o sistema de tipos já elimina muitas categorias de bug. As que sobram — bugs lógicos, de borda, de parsing, de serialização — são exatamente onde property-based testing brilha. Para quem busca [vagas Rust](/vagas/) e quer evoluir na [carreira Rust](/carreira/), dominar essa técnica é um diferencial real: ela aparece em entrevistas de backend, infraestrutura e fintechs, e é marca de engenheiras e engenheiros que sabem ir além do caminho feliz.

## proptest: a base de property-based testing em Rust

A crate [proptest](/ecossistema/proptest/) é o padrão de fato para property-based testing em Rust, inspirada no QuickCheck de Haskell. A diferença central em relação ao QuickCheck original é que o proptest usa **strategies** — geradores composicionais e determinísticos — em vez de depender de implementações `Arbitrary` acopladas a tipos. Isso dá controle fino sobre o espaço de entradas e torna a geração reproduzível.

A macro `proptest!` transforma um bloco declarativo em um teste que roda `CASES` iterações (por padrão 256). Veja o exemplo canônico: testar que ordenar duas vezes é o mesmo que ordenar uma vez (idempotência do sort).

```toml
[dev-dependencies]
proptest = "1.5"
```

```rust
use proptest::prelude::*;

fn ordenar(v: &mut [i32]) {
    v.sort_unstable();
}

proptest! {
    #[test]
    fn ordenar_duas_vezes_equivale_a_uma(ref mut v in prop::collection::vec(-1000i32..1000, 0..100)) {
        let mut esperado = v.clone();
        ordenar(&mut esperado);

        ordenar(v);
        ordenar(v);

        prop_assert_eq!(v, &esperado);
    }
}
```

Se rodar `cargo test`, o proptest gera 256 vetores aleatórios e verifica a propriedade para cada um. Se algum quebrar, ele encolhe automaticamente até o menor vetor que falha e escreve a semente no arquivo `proptest.regress`, ao lado do teste. Na próxima execução, aquele caso específico é retestado junto aos gerados, garantindo que a regressão nunca suma silenciosamente.

## Strategies: descrevendo o universo de entradas

O coração do proptest são as **strategies**, geradores de valores tipados que descrevem quais entradas são válidas para o seu domínio. A função `any::<T>()` gera qualquer valor do tipo `T`. Operadores como `prop_map`, `prop_flat_map`, `prop_filter` e o módulo `prop::collection` permitem composição rica.

Um padrão comum é gerar pares de valores onde o segundo depende do primeiro. Por exemplo, para testar uma função de busca binária, você precisa de um vetor ordenado e um elemento que pode ou não estar presente:

```rust
use proptest::prelude::*;

fn busca_binaria(v: &[i32], alvo: i32) -> Option<usize> {
    v.binary_search(&alvo).ok()
}

proptest! {
    #[test]
    fn busca_binaria_encontra_ou_indica_ausencia(
        mut v in prop::collection::vec(any::<i32>(), 0..200).prop_map(|mut xs| {
            xs.sort_unstable();
            xs.dedup();
            xs
        }),
        indice in 0usize..200,
    ) {
        let alvo = if v.is_empty() {
            0
        } else {
            v[indice % v.len()]
        };

        match busca_binaria(&v, alvo) {
            Some(pos) => prop_assert_eq!(v[pos], alvo),
            None => prop_assert!(v.iter().all(|&x| x != alvo)),
        }
    }
}
```

Esse teste cobre, de forma automática, casos que exemplos manuais raramente alcançam: vetor vazio, vetor com um elemento, elemento no início, no fim, duplicatas adjacentes, elemento ausente. Cada execução com semente diferente explora um canto diferente do espaço de entrada.

## Shrinking: por que o contraexemplo fica mínimo

Quando uma propriedade falha, o que importa não é apenas "falhou", mas "falhou por quê". Um contraexemplo de 12 campos aninhados é quase impossível de diagnosticar. O shrinking resolve isso: após detectar a falha, o framework reduz cada componente da entrada — inteiros em direção a zero, strings em direção a vazio, coleções em direção a um elemento — e mantém só o que ainda reproduz o bug.

O resultado é que a falha aparece como "entrada `vec![-1, 0]` quebra o invariant", em vez de "entrada `vec![8372, -19, 44, ...]` quebra". Isso reduz o tempo entre "teste vermelho" e "fix deployable" de horas para minutos.

O shrinking do proptest é determinístico e composicional: cada strategy sabe como encolher seus valores, e strategies compostas reduzem cada parte de forma coordenada. Por isso vale a pena escrever strategies específicas para o seu domínio, em vez de usar `any::<T>()` para tudo — o shrinking fica muito mais útil.

## Quando proptest não chega: entra o cargo-fuzz

proptest é excelente para lógica de alto nível. Mas existem domínios onde a entrada é fundamentalmente binária e não estruturada: parsers de formato de arquivo, decodificadores de protocolo, desserializadores que consomem bytes vindos da rede, validadores de token. Para esses, o relevante não é gerar entradas "válidas e composicionais", mas jogar bytes arbitrários na fronteira e ver se algo quebra, trava, entra em loop ou viola um invariant de segurança.

É onde entra o [cargo-fuzz](https://rust-fuzz.github.io/book/cargo-fuzz.html), que empacota o libFuzzer do LLVM com instrumentação de cobertura. Diferente do proptest, que gera entradas independentes, o cargo-fuzz usa feedback de cobertura para evoluir entradas em direção a caminhos de código ainda não explorados. É fuzzing orientado por cobertura.

Instale e inicialize:

```bash
cargo install cargo-fuzz
cargo fuzz add decodifica_payload
```

Isso cria um target em `fuzz/fuzz_targets/decodifica_payload.rs`:

```rust
#![no_main]
use libfuzzer_sys::fuzz_target;

fuzz_target!(|data: &[u8]| {
    // nunca panicar; deixar o bug aparecer como bug ou assertion
    if let Ok(decodificado) = meu_parser::decodifica(data) {
        // invariant: decodificado.len() <= data.len()
        assert!(decodificado.len() <= data.len());
    }
});
```

Rode com:

```bash
cargo fuzz run decodifica_payload -- -max_total_time=120
```

O libFuzzer vai gerar entradas, medir cobertura, mutar e evoluir. Entradas que disparam panics, overflows, asserts ou loops infinitos são salvas em `fuzz/artifacts/`. Para um servidor que recebe JSON de clientes não confiáveis, meia hora de fuzzing costuma revelar bordas que testes manuais de meses ignoraram.

## Combinando proptest e cargo-fuzz

Os dois não competem, formam um pipeline. Um padrão maduro em bases de código Rust de qualidade:

1. **proptest** cobre invariantes da lógica de domínio: funções puras, transformações, parsers de texto, serialização round-trip com [Serde](/ecossistema/serde/). Rápido, integrado ao `cargo test`, roda em todo CI.
2. **cargo-fuzz** cobre a fronteira de bytes não confiáveis: formatos binários, protocolos de rede, decodificadores de imagem, parsers de configuração. Mais lento, roda em job separado ou máquina dedicada, às vezes por horas.

Um exemplo concreto de round-trip com Serde e proptest:

```rust
proptest! {
    #[test]
    fn serde_roundtrip_preserva_registro(
        nome in "[a-zá-ú ]{1,40}",
        idade in 0u8..120,
        saldo in -1_000_000i64..1_000_000,
    ) {
        let reg = Registro { nome, idade, saldo };
        let json = serde_json::to_string(&reg).unwrap();
        let volta: Registro = serde_json::from_str(&json).unwrap();
        prop_assert_eq!(reg, volta);
    }
}
```

Esse teste, com 256 iterações, já teria pegado bugs clássicos de serialização de inteiros negativos, de perda de precisão decimal e de problemas com acentos em JSON mal configurado. Com custo quase zero.

## Integração com CI: determinismo sem perder exploração

Um receio comum é "proptest falha aleatoriamente no CI". O problema é real quando mal configurado, mas tem solução simples. Três práticas resolvem:

Primeiro, fixe a semente no CI. O proptest aceita `PROPTEST_CASES` e `PROPTEST_MAX_SHRINK_ITERS` como variáveis de ambiente, e a macro `proptest!` aceita configuração inline (`#cfg(proptest)`). Para CI, reduza casos a um número previsível e mantenha a exploração completa local ou em job noturno.

Segundo, versione o arquivo `proptest.regress`. Ele contém as entradas que já falharam e deve ser comitado. Assim, mesmo com sementes diferentes, as regressões conhecidas sempre são retestadas. Esse arquivo é a memória do sistema sobre bordas descobertas.

Terceiro, rode fuzzing em job separado, com tempo limitado, e armazene o corpus (`fuzz/corpus/`) em artefato. O corpus é o acúmulo de entradas interessantes que o libFuzzer descobriu; reusá-lo acelera futuras sessões.

## Quando property-based testing vale o investimento

Nem todo código precisa de proptest. Uma função `soma(a, b) -> a + b` não. Property-based testing paga o custo de escrita quando o domínio tem: muitas bordas, invariantes não triviais, parsing de entrada externa, serialização/desserialização, estruturas de dados (árvores, grafos, filas), aritmética com limites, ou manipulação de texto Unicode.

Para medir se valerá a pena, uma heurística útil: se você consegue enunciar a propriedade em uma frase ("a saída é sempre ordenada", "decodificar o que codifiquei devolve o original", "o tamanho nunca diminui após inserir"), então proptest encaixa. Se a única coisa que você consegue afirmar é "para esta entrada, a saída é aquela", fique com teste de exemplo.

Em projetos com [benchmarking estruturado](/ecossistema/criterion/) e [testes de estratégia](/tutoriais/testes-rust/) já maduros, adicionar proptest é o próximo degrau de qualidade. Ele fecha o ciclo: criterion mede desempenho, testes unitários verificam contrato, proptest explora bordas e cargo-fuzz stressa a fronteira hostil. Junto, transformam "funciona nos meus testes" em "funciona sob entrada que ninguém imaginou".

## Conclusão

Testes baseados em propriedades e fuzzing em Rust, com proptest e cargo-fuzz, são as ferramentas que separam código que "passa nos testes" de código que sobrevive a entrada hostil. O custo de adotá-las é baixo — uma dev-dependency, uma macro, um target de fuzz — e o retorno aparece em bugs que jamais teriam sido encontrados manualmente, em contraexemplos já minimizados prontos para fix, e em confiança real para operar em produção.

Para quem está construindo portfólio ou se preparando para entrevistas, dominar esse ferramental é diferencial concreto. Comece pelo round-trip de serialização, adicione invariantes às suas estruturas de dados, e reserve uma hora de fuzzing para qualquer parser que consuma bytes externos. O ecossistema brasileiro de empresas que usam Rust — fintechs, logtechs, plataformas de dados — valoriza exatamente esse perfil de engenharia que entende qualidade não como cobertura percentual, mas como resistência ao inesperado.

---

**Leia também:**
- [Estratégias de Testes em Rust: Boas Práticas](/blog/testes-rust-estrategias-boas-praticas-2026/)
- [Tratamento de Erros com thiserror e anyhow](/blog/tratamento-erros-rust-thiserror-anyhow/)
- [Validação de Dados em Rust: validator, garde e Serde](/blog/rust-validacao-dados-validator-garde-serde-2026/)
- [Proptest — Property-Based Testing](/ecossistema/proptest/)
- [Criterion — Benchmarking Estatístico](/ecossistema/criterion/)
- [Serde — Serialização em Rust](/ecossistema/serde/)

**Veja também nossos sites parceiros:**
- <a href="https://golang.com.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Go Brasil</a> — comparando fuzzing e property-based testing entre Rust e Go
- <a href="https://python.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'python.dev.br' })">Python Brasil Dev</a> — do Hypothesis em Python ao proptest em Rust
- <a href="https://kotlin.dev.br/" target="_blank" rel="noopener" onclick="umami.track('portfolio-site-click', { destination: 'kotlin.dev.br' })">Kotlin Brasil Dev</a> — estratégias de teste em ecossistemas distintos
