Rust

Testes Avançados — Propriedades, Mocks e Benchmarks Já leu

17 min de leitura

Testes Avançados — Propriedades, Mocks e Benchmarks
No Artigo #16 cobrimos os fundamentos de testes em Rust — testes unitários, de integração, e as macros de asserção. Esses fundamentos são suficientes para a maioria dos projetos, mas há uma categoria inteira de técnicas

 

No Artigo #16 cobrimos os fundamentos de testes em Rust — testes unitários, de integração, e as macros de asserção. Esses fundamentos são suficientes para a maioria dos projetos, mas há uma categoria inteira de técnicas que elevam a qualidade dos testes a outro nível: property-based testing, mocks e benchmarks.

Cada uma resolve um problema diferente. Property-based testing encontra casos que você nunca pensaria em testar. Mocks isolam o código que você quer testar de suas dependências. Benchmarks medem se seu código é rápido o suficiente — e se ficou mais lento após uma mudança.


Property-Based Testing com proptest

Testes baseados em exemplos — como os que escrevemos até agora — verificam casos específicos: "dado entrada X, espero saída Y". O problema é que você só testa os casos que imaginou. Bugs vivem nos casos que você não imaginou.

Property-based testing inverte a abordagem: em vez de escrever exemplos, você descreve propriedades que devem ser verdadeiras para qualquer entrada válida. A biblioteca gera centenas de entradas aleatórias e verifica se a propriedade se mantém.

[dev-dependencies]
proptest = "1"

Conceito fundamental

use proptest::prelude::*;

fn ordenar(mut v: Vec<i32>) -> Vec<i32> {
    v.sort();
    v
}

proptest! {
    // Esta propriedade deve ser verdadeira para QUALQUER Vec<i32>
    #[test]
    fn ordenar_mantem_tamanho(v: Vec<i32>) {
        let ordenado = ordenar(v.clone());
        // Propriedade 1: tamanho não muda
        prop_assert_eq!(ordenado.len(), v.len());
    }

    #[test]
    fn ordenar_e_idempotente(v: Vec<i32>) {
        let uma_vez = ordenar(v.clone());
        let duas_vezes = ordenar(uma_vez.clone());
        // Propriedade 2: ordenar duas vezes = ordenar uma vez
        prop_assert_eq!(uma_vez, duas_vezes);
    }

    #[test]
    fn ordenar_produz_sequencia_nao_decrescente(v: Vec<i32>) {
        let ordenado = ordenar(v);
        // Propriedade 3: cada elemento ≤ próximo
        for janela in ordenado.windows(2) {
            prop_assert!(janela[0] <= janela[1]);
        }
    }

    #[test]
    fn ordenar_contem_mesmos_elementos(v: Vec<i32>) {
        let mut original = v.clone();
        let mut ordenado = ordenar(v);
        // Propriedade 4: mesmos elementos (comparação após ordenar ambos)
        original.sort();
        prop_assert_eq!(original, ordenado);
    }
}

Execute com cargo test — proptest gera 256 casos por padrão para cada propriedade.

Quando proptest encontra uma falha

A grande qualidade do proptest é o shrinking automático. Quando encontra uma entrada que falha, ele a reduz ao menor exemplo possível:

use proptest::prelude::*;

// Função com bug intencional: falha para números > 1000
fn processar(n: i32) -> i32 {
    if n > 1000 {
        panic!("Não esperava número tão grande!");
    }
    n * 2
}

proptest! {
    #[test]
    fn processar_sempre_dobra(n: i32) {
        // Esta propriedade vai falhar
        prop_assert_eq!(processar(n), n * 2);
    }
}

Saída:

FAILED. Shrinking...
Shrunk to n = 1001
thread 'processar_sempre_dobra' panicked at 'Não esperava número tão grande!'

Em vez de reportar o número aleatório original (ex: 847392), proptest reduz ao menor valor que reproduz o bug: 1001.

Estratégias customizadas

Proptest tem estratégias para gerar tipos complexos:

use proptest::prelude::*;
use proptest::collection::vec;

#[derive(Debug, Clone)]
struct Produto {
    nome: String,
    preco: f64,
    quantidade: u32,
}

fn estrategia_produto() -> impl Strategy<Value = Produto> {
    (
        "[a-zA-Z]{3,20}",     // nome: string com 3-20 letras
        0.01f64..10000.0f64,   // preco: entre 0.01 e 10000
        0u32..1000u32,         // quantidade: entre 0 e 999
    )
        .prop_map(|(nome, preco, quantidade)| Produto {
            nome,
            preco,
            quantidade,
        })
}

fn calcular_total(produtos: &[Produto]) -> f64 {
    produtos.iter().map(|p| p.preco * p.quantidade as f64).sum()
}

proptest! {
    #[test]
    fn total_nunca_e_negativo(
        produtos in vec(estrategia_produto(), 0..20)
    ) {
        let total = calcular_total(&produtos);
        prop_assert!(total >= 0.0, "Total negativo: {total}");
    }

    #[test]
    fn total_com_lista_vazia_e_zero(
        _dummy in Just(())
    ) {
        prop_assert_eq!(calcular_total(&[]), 0.0);
    }

    #[test]
    fn adicionar_produto_aumenta_ou_mantem_total(
        mut produtos in vec(estrategia_produto(), 1..10),
        novo in estrategia_produto(),
    ) {
        let total_antes = calcular_total(&produtos);
        produtos.push(novo);
        let total_depois = calcular_total(&produtos);
        prop_assert!(total_depois >= total_antes);
    }
}

Aplicando proptest na API de tarefas

use proptest::prelude::*;

// Testa propriedades do modelo Tarefa
proptest! {
    #[test]
    fn titulo_sem_espacos_extremos_apos_trim(
        espacos_antes in " {0,10}",
        conteudo in "[a-zA-ZÀ-ÿ ]{1,100}",
        espacos_depois in " {0,10}",
    ) {
        let titulo_bruto = format!("{espacos_antes}{conteudo}{espacos_depois}");
        let titulo_limpo = titulo_bruto.trim().to_string();

        // Propriedade: após trim, não começa nem termina com espaço
        prop_assert!(!titulo_limpo.starts_with(' '));
        prop_assert!(!titulo_limpo.ends_with(' '));
        // Propriedade: o conteúdo original está preservado
        prop_assert!(titulo_limpo.contains(conteudo.trim()));
    }

    #[test]
    fn paginacao_nunca_retorna_mais_que_solicitado(
        total_itens in 0usize..200usize,
        por_pagina in 1usize..50usize,
        pagina in 1usize..20usize,
    ) {
        // Simula paginação
        let itens: Vec<i32> = (0..total_itens as i32).collect();
        let inicio = (pagina - 1) * por_pagina;
        let pagina_resultado: Vec<&i32> = itens
            .iter()
            .skip(inicio)
            .take(por_pagina)
            .collect();

        // Propriedade: página nunca tem mais itens que por_pagina
        prop_assert!(pagina_resultado.len() <= por_pagina);
    }
}

Mocks com mockall

Mocks permitem testar código que depende de serviços externos — banco de dados, APIs HTTP, sistemas de email — sem realmente usar esses serviços. Você substitui a dependência real por uma versão simulada que você controla.

[dev-dependencies]
mockall = "0.12"

Definindo traits mockáveis

O segredo dos mocks em Rust é definir dependências como traits. O código de produção usa a implementação real; os testes usam o mock:

use mockall::{automock, predicate::*};
use uuid::Uuid;
use crate::modelo::Tarefa;
use crate::erro::ResultadoApi;

// A anotação #[automock] gera automaticamente MockRepositorioTarefas
#[automock]
pub trait RepositorioTarefasTrait: Send + Sync {
    async fn criar(&self, tarefa: &Tarefa) -> ResultadoApi<()>;
    async fn buscar_por_id(&self, id: &Uuid) -> ResultadoApi<Tarefa>;
    async fn deletar(&self, id: &Uuid) -> ResultadoApi<()>;
}

#[automock]
pub trait ServicoEmailTrait: Send + Sync {
    async fn enviar_confirmacao(&self, email: &str, nome: &str) -> ResultadoApi<()>;
    async fn enviar_notificacao(&self, email: &str, mensagem: &str) -> ResultadoApi<()>;
}

Serviço usando os traits

use std::sync::Arc;

pub struct ServicoCriarTarefa<R, E>
where
    R: RepositorioTarefasTrait,
    E: ServicoEmailTrait,
{
    repo: Arc<R>,
    email: Arc<E>,
}

impl<R: RepositorioTarefasTrait, E: ServicoEmailTrait> ServicoCriarTarefa<R, E> {
    pub fn novo(repo: Arc<R>, email: Arc<E>) -> Self {
        ServicoCriarTarefa { repo, email }
    }

    pub async fn executar(
        &self,
        titulo: String,
        email_usuario: &str,
        nome_usuario: &str,
    ) -> ResultadoApi<Tarefa> {
        use crate::modelo::Prioridade;

        if titulo.trim().is_empty() {
            return Err(crate::erro::ErroApi::TituloVazio);
        }

        let tarefa = Tarefa::nova(titulo.trim().to_string(), None, Prioridade::Media);

        self.repo.criar(&tarefa).await?;

        // Envia notificação por email
        self.email
            .enviar_notificacao(
                email_usuario,
                &format!("Tarefa '{}' criada com sucesso!", tarefa.titulo),
            )
            .await?;

        Ok(tarefa)
    }
}

Testes com mocks

#[cfg(test)]
mod tests {
    use super::*;
    use mockall::predicate::*;
    use std::sync::Arc;

    #[tokio::test]
    async fn criar_tarefa_chama_repo_e_email() {
        let mut mock_repo  = MockRepositorioTarefasTrait::new();
        let mut mock_email = MockServicoEmailTrait::new();

        // Configura expectativa: criar() deve ser chamado uma vez
        mock_repo
            .expect_criar()
            .times(1)
            .returning(|_| Box::pin(async { Ok(()) }));

        // Configura expectativa: enviar_notificacao() com email específico
        mock_email
            .expect_enviar_notificacao()
            .with(eq("ana@exemplo.com"), always())
            .times(1)
            .returning(|_, _| Box::pin(async { Ok(()) }));

        let servico = ServicoCriarTarefa::novo(
            Arc::new(mock_repo),
            Arc::new(mock_email),
        );

        let resultado = servico
            .executar(
                "Minha tarefa".to_string(),
                "ana@exemplo.com",
                "Ana",
            )
            .await;

        assert!(resultado.is_ok());
        assert_eq!(resultado.unwrap().titulo, "Minha tarefa");
    }

    #[tokio::test]
    async fn titulo_vazio_nao_chama_repo() {
        let mut mock_repo  = MockRepositorioTarefasTrait::new();
        let mut mock_email = MockServicoEmailTrait::new();

        // Configura expectativa: criar() NÃO deve ser chamado
        mock_repo.expect_criar().times(0);
        mock_email.expect_enviar_notificacao().times(0);

        let servico = ServicoCriarTarefa::novo(
            Arc::new(mock_repo),
            Arc::new(mock_email),
        );

        let resultado = servico
            .executar("   ".to_string(), "ana@exemplo.com", "Ana")
            .await;

        assert!(resultado.is_err());
    }

    #[tokio::test]
    async fn falha_no_repo_nao_envia_email() {
        use crate::erro::ErroApi;

        let mut mock_repo  = MockRepositorioTarefasTrait::new();
        let mut mock_email = MockServicoEmailTrait::new();

        // Repo falha
        mock_repo
            .expect_criar()
            .times(1)
            .returning(|_| Box::pin(async { Err(ErroApi::ErroInterno) }));

        // Email NÃO deve ser enviado se repo falhar
        mock_email.expect_enviar_notificacao().times(0);

        let servico = ServicoCriarTarefa::novo(
            Arc::new(mock_repo),
            Arc::new(mock_email),
        );

        let resultado = servico
            .executar("Tarefa".to_string(), "ana@exemplo.com", "Ana")
            .await;

        assert!(resultado.is_err());
    }

    #[tokio::test]
    async fn mock_retorna_tarefa_especifica() {
        use crate::modelo::Prioridade;

        let tarefa_esperada = Tarefa::nova(
            "Tarefa específica".to_string(),
            None,
            Prioridade::Alta,
        );
        let id = tarefa_esperada.id;
        let clone = tarefa_esperada.clone();

        let mut mock_repo = MockRepositorioTarefasTrait::new();

        mock_repo
            .expect_buscar_por_id()
            .with(eq(id))
            .times(1)
            .return_once(move |_| {
                Box::pin(async move { Ok(clone) })
            });

        let resultado = mock_repo.buscar_por_id(&id).await.unwrap();
        assert_eq!(resultado.titulo, "Tarefa específica");
    }
}

Mock de cliente HTTP

Um caso muito comum é mockar chamadas HTTP externas:

#[automock]
pub trait ClienteHttpTrait: Send + Sync {
    async fn get_json(&self, url: &str) -> ResultadoApi<serde_json::Value>;
    async fn post_json(
        &self,
        url: &str,
        corpo: serde_json::Value,
    ) -> ResultadoApi<serde_json::Value>;
}

pub struct ServicoPrevisao<C: ClienteHttpTrait> {
    cliente: Arc<C>,
    url_base: String,
}

impl<C: ClienteHttpTrait> ServicoPrevisao<C> {
    pub async fn buscar_temperatura(&self, cidade: &str) -> ResultadoApi<f64> {
        let url = format!("{}/tempo/{cidade}", self.url_base);
        let resposta = self.cliente.get_json(&url).await?;

        resposta["temperatura"]
            .as_f64()
            .ok_or(crate::erro::ErroApi::DadosInvalidos(
                "Campo 'temperatura' ausente".to_string(),
            ))
    }
}

#[cfg(test)]
mod tests_http {
    use super::*;
    use serde_json::json;

    #[tokio::test]
    async fn buscar_temperatura_parseia_resposta() {
        let mut mock = MockClienteHttpTrait::new();

        mock.expect_get_json()
            .with(contains("Curitiba"))
            .times(1)
            .returning(|_| {
                Box::pin(async {
                    Ok(json!({ "temperatura": 18.5, "umidade": 72 }))
                })
            });

        let servico = ServicoPrevisao {
            cliente: Arc::new(mock),
            url_base: "https://api.tempo.exemplo".to_string(),
        };

        let temp = servico.buscar_temperatura("Curitiba").await.unwrap();
        assert!((temp - 18.5).abs() < 0.001);
    }

    #[tokio::test]
    async fn buscar_temperatura_propaga_erro_http() {
        let mut mock = MockClienteHttpTrait::new();

        mock.expect_get_json()
            .times(1)
            .returning(|_| {
                Box::pin(async {
                    Err(crate::erro::ErroApi::ErroInterno)
                })
            });

        let servico = ServicoPrevisao {
            cliente: Arc::new(mock),
            url_base: "https://api.tempo.exemplo".to_string(),
        };

        assert!(servico.buscar_temperatura("Curitiba").await.is_err());
    }
}

Benchmarks com criterion

Benchmarks medem performance de forma rigorosa — com análise estatística, múltiplas amostras e detecção de regressões. A crate criterion é o padrão para benchmarks em Rust.

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }

[[bench]]
name = "benchmarks"
harness = false

benches/benchmarks.rs:

use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
use std::collections::HashMap;

// Funções a serem comparadas
fn busca_linear(lista: &[i32], alvo: i32) -> Option<usize> {
    lista.iter().position(|&x| x == alvo)
}

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

fn contar_palavras_loop(texto: &str) -> HashMap<&str, usize> {
    let mut contagem = HashMap::new();
    for palavra in texto.split_whitespace() {
        *contagem.entry(palavra).or_insert(0) += 1;
    }
    contagem
}

fn contar_palavras_fold(texto: &str) -> HashMap<&str, usize> {
    texto.split_whitespace().fold(HashMap::new(), |mut acc, palavra| {
        *acc.entry(palavra).or_insert(0) += 1;
        acc
    })
}

fn bench_busca(c: &mut Criterion) {
    let mut grupo = c.benchmark_group("Busca");

    for tamanho in [100, 1_000, 10_000, 100_000] {
        let lista: Vec<i32> = (0..tamanho).collect();
        let alvo = tamanho / 2; // elemento no meio

        grupo.throughput(Throughput::Elements(tamanho as u64));

        grupo.bench_with_input(
            BenchmarkId::new("linear", tamanho),
            &(&lista, alvo),
            |b, (lista, alvo)| {
                b.iter(|| busca_linear(black_box(lista), black_box(*alvo)))
            },
        );

        grupo.bench_with_input(
            BenchmarkId::new("binaria", tamanho),
            &(&lista, alvo),
            |b, (lista, alvo)| {
                b.iter(|| busca_binaria(black_box(lista), black_box(*alvo)))
            },
        );
    }

    grupo.finish();
}

fn bench_contagem_palavras(c: &mut Criterion) {
    let textos = [
        ("pequeno", "o rato roeu a roupa do rei de roma"),
        ("medio", &"palavra ".repeat(100)),
        ("grande", &"rust é incrível ".repeat(1000)),
    ];

    let mut grupo = c.benchmark_group("ContarPalavras");

    for (nome, texto) in &textos {
        grupo.bench_with_input(
            BenchmarkId::new("loop", nome),
            texto,
            |b, t| b.iter(|| contar_palavras_loop(black_box(t))),
        );

        grupo.bench_with_input(
            BenchmarkId::new("fold", nome),
            texto,
            |b, t| b.iter(|| contar_palavras_fold(black_box(t))),
        );
    }

    grupo.finish();
}

fn bench_alocacao(c: &mut Criterion) {
    let mut grupo = c.benchmark_group("Alocação");

    grupo.bench_function("vec_push_sem_capacidade", |b| {
        b.iter(|| {
            let mut v = Vec::new();
            for i in 0..1000 {
                v.push(black_box(i));
            }
            v
        })
    });

    grupo.bench_function("vec_push_com_capacidade", |b| {
        b.iter(|| {
            let mut v = Vec::with_capacity(1000);
            for i in 0..1000 {
                v.push(black_box(i));
            }
            v
        })
    });

    grupo.bench_function("collect_de_range", |b| {
        b.iter(|| {
            let v: Vec<i32> = (0..1000).collect();
            black_box(v)
        })
    });

    grupo.finish();
}

criterion_group!(benches, bench_busca, bench_contagem_palavras, bench_alocacao);
criterion_main!(benches);

Execute com:

cargo bench

Saída típica:

Busca/linear/100        time: [245.32 ns 246.18 ns 247.12 ns]
Busca/binaria/100       time: [12.45 ns 12.51 ns 12.58 ns]

Busca/linear/100000     time: [245.32 µs 246.18 µs 247.12 µs]
Busca/binaria/100000    time: [18.23 ns 18.31 ns 18.40 ns]

ContarPalavras/loop/pequeno   time: [312 ns 315 ns 318 ns]
ContarPalavras/fold/pequeno   time: [308 ns 311 ns 314 ns]

Alocação/vec_push_sem_capacidade  time: [8.45 µs 8.52 µs 8.60 µs]
Alocação/vec_push_com_capacidade  time: [2.31 µs 2.33 µs 2.35 µs]
Alocação/collect_de_range         time: [1.89 µs 1.91 µs 1.93 µs]

Criterion gera relatórios HTML em target/criterion/ com gráficos de distribuição e histórico de performance.

Detectando regressões

Para detectar regressões de performance automaticamente, salve uma baseline:

# Salvar baseline atual
cargo bench -- --save-baseline main

# Após mudanças no código, comparar com baseline
cargo bench -- --baseline main

Se o código ficou mais lento, criterion reporta:

Busca/linear/1000   Performance has regressed.
  change: [+23.5% +24.8% +26.1%] (p = 0.00 < 0.05)
  Time has increased significantly.

Um programa completo: suíte de testes integrada

Vamos combinar as três técnicas para testar um módulo de processamento de dados:

pub mod processador {
    #[derive(Debug, Clone, PartialEq)]
    pub struct Estatisticas {
        pub min: f64,
        pub max: f64,
        pub media: f64,
        pub mediana: f64,
        pub desvio_padrao: f64,
    }

    pub fn calcular(dados: &[f64]) -> Option<Estatisticas> {
        if dados.is_empty() {
            return None;
        }

        let n = dados.len() as f64;
        let min = dados.iter().cloned().fold(f64::INFINITY, f64::min);
        let max = dados.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
        let soma: f64 = dados.iter().sum();
        let media = soma / n;

        let mut ordenado = dados.to_vec();
        ordenado.sort_by(|a, b| a.partial_cmp(b).unwrap());
        let mediana = if dados.len() % 2 == 0 {
            (ordenado[dados.len() / 2 - 1] + ordenado[dados.len() / 2]) / 2.0
        } else {
            ordenado[dados.len() / 2]
        };

        let variancia = dados.iter()
            .map(|&x| (x - media).powi(2))
            .sum::<f64>() / n;
        let desvio_padrao = variancia.sqrt();

        Some(Estatisticas { min, max, media, mediana, desvio_padrao })
    }

    pub fn normalizar(dados: &[f64]) -> Option<Vec<f64>> {
        let stats = calcular(dados)?;
        let amplitude = stats.max - stats.min;

        if amplitude == 0.0 {
            return Some(vec![0.5; dados.len()]);
        }

        Some(dados.iter()
            .map(|&x| (x - stats.min) / amplitude)
            .collect())
    }
}

#[cfg(test)]
mod testes_unitarios {
    use super::processador::*;

    #[test]
    fn lista_vazia_retorna_none() {
        assert!(calcular(&[]).is_none());
        assert!(normalizar(&[]).is_none());
    }

    #[test]
    fn elemento_unico() {
        let stats = calcular(&[42.0]).unwrap();
        assert_eq!(stats.min, 42.0);
        assert_eq!(stats.max, 42.0);
        assert_eq!(stats.media, 42.0);
        assert_eq!(stats.mediana, 42.0);
        assert_eq!(stats.desvio_padrao, 0.0);
    }

    #[test]
    fn mediana_lista_par() {
        let stats = calcular(&[1.0, 2.0, 3.0, 4.0]).unwrap();
        assert_eq!(stats.mediana, 2.5);
    }

    #[test]
    fn normalizar_produz_valores_entre_0_e_1() {
        let dados = vec![10.0, 20.0, 30.0, 40.0, 50.0];
        let norm = normalizar(&dados).unwrap();
        assert_eq!(norm[0], 0.0);
        assert_eq!(norm[4], 1.0);
        for &v in &norm {
            assert!(v >= 0.0 && v <= 1.0);
        }
    }
}

#[cfg(test)]
mod testes_propriedades {
    use super::processador::*;
    use proptest::prelude::*;

    proptest! {
        #[test]
        fn min_menor_ou_igual_a_max(dados in prop::collection::vec(-1000.0f64..1000.0f64, 1..50)) {
            let stats = calcular(&dados).unwrap();
            prop_assert!(stats.min <= stats.max);
        }

        #[test]
        fn media_entre_min_e_max(dados in prop::collection::vec(-1000.0f64..1000.0f64, 1..50)) {
            let stats = calcular(&dados).unwrap();
            prop_assert!(stats.media >= stats.min - 1e-10);
            prop_assert!(stats.media <= stats.max + 1e-10);
        }

        #[test]
        fn desvio_padrao_nunca_negativo(dados in prop::collection::vec(-1000.0f64..1000.0f64, 1..50)) {
            let stats = calcular(&dados).unwrap();
            prop_assert!(stats.desvio_padrao >= 0.0);
        }

        #[test]
        fn normalizados_entre_0_e_1(dados in prop::collection::vec(-1000.0f64..1000.0f64, 2..50)) {
            if let Some(norm) = normalizar(&dados) {
                for &v in &norm {
                    prop_assert!(v >= -1e-10 && v <= 1.0 + 1e-10,
                        "Valor fora do intervalo [0,1]: {v}");
                }
            }
        }

        #[test]
        fn tamanho_preservado_na_normalizacao(dados in prop::collection::vec(-1000.0f64..1000.0f64, 1..50)) {
            if let Some(norm) = normalizar(&dados) {
                prop_assert_eq!(norm.len(), dados.len());
            }
        }
    }
}

Quando usar cada técnica

Com três ferramentas poderosas, a questão é saber quando usar cada uma:

Testes baseados em exemplos são o ponto de partida. Use para comportamentos específicos, casos de erro conhecidos, e documentação de como o código deve se comportar. São fáceis de escrever e de ler.

Property-based testing complementa testes de exemplo para código com domínio amplo — funções matemáticas, parsers, algoritmos de ordenação, serializadores. Use quando a propriedade é mais fácil de descrever do que enumerar exemplos. Especialmente valioso para encontrar bugs em casos extremos.

Mocks são essenciais para código com efeitos colaterais — I/O, rede, banco de dados, tempo. Use para isolar o código que você quer testar de suas dependências externas. Evite mocks para dependências internas — prefira usar as implementações reais.

Benchmarks pertencem a um momento específico: quando você precisa provar que o código é rápido o suficiente, ou verificar que uma otimização realmente melhorou a performance. Não escreva benchmarks prematuramente — primeiro faça funcionar, depois meça, depois otimize.


Fontes e leituras recomendadas

  • proptest documentation — https://docs.rs/proptest
  • proptest Book — guia completo — https://proptest-rs.github.io/proptest/
  • mockall documentation — https://docs.rs/mockall
  • criterion documentation — https://docs.rs/criterion
  • "Criterion.rs User Guide" — https://bheisler.github.io/criterion.rs/book/
  • "Property-Based Testing with PropTest" — blog.logrocket.com — introdução prática — https://blog.logrocket.com/property-based-testing-in-rust-with-proptest/
  • "Testing" — Rust by Example — https://doc.rust-lang.org/rust-by-example/testing.html
  • "Zero to Production in Rust" — Luca Palmieri — cap. sobre testes de integração — https://www.zero2prod.com
Comentários

Mais em Rust

Rust para Data Science — Processamento de Dados com Polars e ndarray
Rust para Data Science — Processamento de Dados com Polars e ndarray

Rust — Artigo #41 Rust para Data Science — Processamento de Dados com Polars...

Generics — Código que Funciona para Qualquer Tipo
Generics — Código que Funciona para Qualquer Tipo

&nbsp; No artigo anterior aprendemos que traits definem&nbsp;o que um tipo p...

WebAssembly com Rust — Executando Rust no Navegador
WebAssembly com Rust — Executando Rust no Navegador

Rust e WebAssembly formam uma das combinações mais empolgantes da programação...