---
title: "cargo-mutants: Testes de Mutação em Rust | Rust Brasil"
url: "https://rustlang.com.br/blog/cargo-mutants-testes-mutacao-rust-ci-2026/"
markdown_url: "https://rustlang.com.br/blog/cargo-mutants-testes-mutacao-rust-ci-2026.MD"
description: "Use cargo-mutants para medir se seus testes Rust detectam bugs reais. Guia com mutantes, relatório, exclusões, CI, performance e exemplos práticos."
date: "2026-08-16"
author: "Equipe Rust Brasil"
---

# cargo-mutants: Testes de Mutação em Rust | Rust Brasil

Use cargo-mutants para medir se seus testes Rust detectam bugs reais. Guia com mutantes, relatório, exclusões, CI, performance e exemplos práticos.


**Use `cargo-mutants` quando você quiser descobrir se seus testes realmente detectam bugs, e não apenas se executam o código.** A ferramenta modifica temporariamente pequenos trechos do programa — por exemplo, troca `>` por `>=`, remove uma chamada ou substitui um valor retornado — e roda a suíte para verificar se ela falha. Se os testes continuam verdes, o mutante “sobreviveu” e existe uma lacuna a investigar.

Esse processo é chamado de **teste de mutação** (*mutation testing*). Ele complementa testes unitários, integração, cobertura e fuzzing com uma pergunta muito prática: **se alguém introduzisse este defeito plausível, a suíte impediria o merge?**

O fluxo mínimo é instalar a ferramenta, confirmar que `cargo test` já passa e executar:

```bash
cargo install cargo-mutants --locked
cargo test
cargo mutants
```

Como cada mutante pode exigir uma nova execução dos testes, comece por uma crate, módulo ou arquivo de alto risco. Este guia explica como interpretar o relatório, transformar sobreviventes úteis em testes, evitar métricas vaidosas e integrar `cargo-mutants` à CI sem tornar cada pull request interminável.

## Resposta rápida: quando usar cargo-mutants

| Situação | Recomendação |
|---|---|
| Biblioteca pequena com suíte rápida | Rode o projeto inteiro na CI ou antes do release |
| Workspace grande | Comece pelas crates alteradas ou públicas |
| Regra de negócio crítica | Priorize o módulo, mesmo com cobertura alta |
| Parser ou entrada hostil | Combine mutação com proptest e fuzzing |
| Código gerado | Exclua ou limite a análise, com justificativa |
| Testes lentos ou flaky | Corrija a suíte antes de confiar no resultado |
| Primeiro contato | Rode em um arquivo pequeno e leia cada sobrevivente |

A ferramenta é mais valiosa em código com decisões observáveis: validações, cálculos, permissões, estados, parsers, estruturas de dados e APIs de biblioteca. Ela tende a produzir menos retorno em getters triviais, código gerado ou caminhos que apenas delegam chamadas sem contrato próprio.

## O que é teste de mutação

Imagine esta função:

```rust
pub fn pode_sacar(saldo: i64, valor: i64) -> bool {
    valor > 0 && saldo >= valor
}
```

Uma suíte superficial pode conter apenas:

```rust
#[test]
fn permite_saque_com_saldo_suficiente() {
    assert!(pode_sacar(100, 40));
}
```

O teste executa a função e passa. Uma ferramenta de cobertura provavelmente marcará a linha como coberta. Mesmo assim, vários bugs poderiam entrar sem serem percebidos:

- trocar `saldo >= valor` por `saldo > valor`;
- remover a validação `valor > 0`;
- retornar sempre `true`;
- inverter uma condição;
- aceitar um saque exatamente acima do saldo.

Um mecanismo de mutação cria alterações desse tipo, uma de cada vez, recompila quando necessário e executa os testes. O resultado de cada tentativa costuma cair em uma destas categorias conceituais:

- **capturado (*caught*):** algum teste falhou; a suíte detectou a mudança;
- **sobrevivente (*missed/survived*):** todos os testes passaram;
- **timeout:** a alteração fez o teste exceder o limite;
- **não compilável (*unviable*):** a mutação não gerou um programa Rust válido;
- **falha de infraestrutura:** build, ambiente ou teste falhou por motivo não relacionado ao comportamento mutado.

Os nomes exatos dos arquivos e estados podem evoluir entre versões. Depois da primeira execução, confira o diretório de saída e use `cargo mutants --help` para entender a versão instalada.

## Por que cobertura de código não basta

Cobertura responde “este código foi executado?”. Mutação responde “os testes perceberam que o comportamento foi alterado?”. São perguntas diferentes.

Considere:

```rust
pub fn aplicar_desconto(preco: u64, percentual: u64) -> u64 {
    preco - (preco * percentual / 100)
}

#[test]
fn calcula_desconto() {
    let resultado = aplicar_desconto(200, 10);
    assert!(resultado < 200);
}
```

A linha está coberta, mas a asserção é fraca. Resultados como `0`, `179` ou `199` também satisfazem `resultado < 200`. Um teste melhor observa o contrato:

```rust
#[test]
fn aplica_dez_por_cento_de_desconto() {
    assert_eq!(aplicar_desconto(200, 10), 180);
}

#[test]
fn percentual_zero_preserva_o_preco() {
    assert_eq!(aplicar_desconto(200, 0), 200);
}
```

A [cobertura com cargo-llvm-cov](/blog/cargo-llvm-cov-cobertura-testes-rust-ci-2026/) continua útil para localizar regiões nunca exercitadas. O `cargo-mutants` aprofunda a análise nas regiões executadas e mostra onde as asserções não distinguem o comportamento correto de uma alteração plausível.

O objetivo não é substituir uma métrica por outra. Use cobertura para encontrar **ausência de execução** e mutação para encontrar **ausência de sensibilidade**.

## Instalando e fazendo a primeira execução

Antes de instalar qualquer ferramenta adicional, deixe a base verde:

```bash
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test --all-features
```

Depois:

```bash
cargo install cargo-mutants --locked
cargo mutants --version
cargo mutants
```

O `--locked` no `cargo install` pede que a instalação respeite o lockfile publicado pela própria ferramenta. Em CI, controle a versão instalada para evitar que uma atualização inesperada altere duração, operadores de mutação ou formato do relatório no meio de uma release.

A primeira execução no repositório inteiro pode demorar. Uma estratégia mais segura é listar a ajuda e escolher um escopo pequeno:

```bash
cargo mutants --help
```

As versões recentes oferecem filtros por pacote, arquivo e nome, além de opções de timeout e paralelismo. Como a interface pode evoluir, não copie flags antigas cegamente para o pipeline. Valide o comando na versão fixada e registre-o em um script do repositório.

## Exemplo prático: um mutante que deve ser capturado

Vamos fortalecer o exemplo do saque:

```rust
#[derive(Debug, PartialEq)]
pub enum ErroSaque {
    ValorInvalido,
    SaldoInsuficiente,
}

pub fn sacar(saldo: i64, valor: i64) -> Result<i64, ErroSaque> {
    if valor <= 0 {
        return Err(ErroSaque::ValorInvalido);
    }

    if valor > saldo {
        return Err(ErroSaque::SaldoInsuficiente);
    }

    Ok(saldo - valor)
}
```

Uma suíte orientada ao contrato cobre o caminho feliz e as fronteiras:

```rust
#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn desconta_o_valor_do_saldo() {
        assert_eq!(sacar(100, 40), Ok(60));
    }

    #[test]
    fn permite_sacar_o_saldo_exato() {
        assert_eq!(sacar(100, 100), Ok(0));
    }

    #[test]
    fn rejeita_valor_acima_do_saldo() {
        assert_eq!(sacar(100, 101), Err(ErroSaque::SaldoInsuficiente));
    }

    #[test]
    fn rejeita_zero() {
        assert_eq!(sacar(100, 0), Err(ErroSaque::ValorInvalido));
    }

    #[test]
    fn rejeita_valor_negativo() {
        assert_eq!(sacar(100, -1), Err(ErroSaque::ValorInvalido));
    }
}
```

Agora mudanças como `valor > saldo` para `valor >= saldo`, `valor <= 0` para `valor < 0` ou `saldo - valor` para outro retorno observável devem fazer pelo menos um teste falhar.

Esse exemplo mostra uma vantagem importante da mutação: ela incentiva testes de **fronteira**. Casos exatamente iguais a zero, limite máximo, coleção vazia, primeiro item e último item costumam separar uma suíte forte de uma suíte que cobre apenas o caminho feliz.

## Como interpretar um mutante sobrevivente

Um sobrevivente é uma pergunta, não um veredito. Faça a triagem nesta ordem.

### 1. A mudança altera um comportamento público?

Se sim, procure o teste que deveria observar esse contrato. Talvez a função seja executada, mas a asserção verifique apenas `is_ok()`, tamanho maior que zero ou ausência de panic.

### 2. Existe um caso de fronteira ausente?

Operadores relacionais são pistas clássicas. Uma troca entre `<` e `<=` pede testes exatamente no limite e nos dois lados dele.

### 3. O mutante é equivalente?

Algumas alterações produzem um programa diferente no texto, mas igual no comportamento possível. Por exemplo, uma condição pode ser redundante devido a um tipo, validação anterior ou invariant garantido pelo construtor. Nenhuma suíte consegue “matar” uma mudança realmente equivalente.

Antes de excluir, documente por que a equivalência é verdadeira. Às vezes a análise revela que o código redundante deveria ser removido em vez de ignorado.

### 4. O código deveria estar no escopo?

Código gerado, bindings externos, implementação derivada, exemplos didáticos ou adaptadores mecânicos podem gerar muito ruído. Exclusões podem ser legítimas, mas devem ser específicas. Excluir um diretório inteiro porque ele possui dois sobreviventes úteis destrói o sinal.

### 5. O teste certo seria caro ou frágil?

Um mutante pode exigir reproduzir falha de rede, relógio, concorrência ou banco. Isso não significa que deve ser ignorado automaticamente. Primeiro tente introduzir uma fronteira testável: trait para dependência externa, relógio injetável, banco temporário, servidor fake ou função pura extraída.

## Melhorando testes sem acoplar à implementação

O maior risco ao reagir a mutantes é escrever testes que conhecem detalhes internos demais. O teste passa a “matar o mutante”, mas impede refactors saudáveis.

Prefira observar:

- retorno e erro de domínio;
- mudança de estado pública;
- mensagem enviada a uma interface;
- conteúdo persistido;
- resposta HTTP;
- evento produzido;
- invariant que deve permanecer verdadeiro.

Evite afirmar:

- número exato de funções privadas chamadas;
- ordem interna sem relevância pública;
- texto completo de erro técnico quando apenas a categoria importa;
- estrutura temporária usada durante o cálculo;
- detalhe de otimização sem contrato de performance.

Em outras palavras, **mate o mutante por meio do contrato**, não por vigilância sobre cada linha da implementação.

## cargo-mutants, proptest e fuzzing

Testes de mutação ficam ainda melhores quando combinados com geração de entradas.

- **Testes por exemplo** documentam casos importantes e regressões conhecidas.
- **proptest** gera valores estruturados e verifica invariantes.
- **cargo-fuzz** explora entradas de bytes com feedback de cobertura.
- **cargo-mutants** altera o programa e mede se a suíte percebe.

Suponha uma função que codifica e decodifica um identificador. Uma propriedade de round-trip pode gerar centenas de valores:

```rust
proptest! {
    #[test]
    fn round_trip_preserva_id(id in any::<u64>()) {
        let texto = codificar(id);
        let recuperado = decodificar(&texto).unwrap();
        prop_assert_eq!(recuperado, id);
    }
}
```

Se `cargo-mutants` alterar parte de `codificar` ou `decodificar`, a variedade do proptest aumenta a chance de capturar a mudança sem criar dezenas de fixtures manuais. Para parsers, o [guia de proptest e cargo-fuzz](/blog/rust-proptest-fuzzing-property-based-testing-2026/) mostra como explorar invariantes e entradas hostis.

A relação também funciona no sentido inverso: sobreviventes podem sugerir uma propriedade geral melhor do que um novo caso isolado.

## Performance: por que o job pode ficar caro

Se a ferramenta encontrar 300 mutantes e a suíte leva 20 segundos, uma execução estritamente sequencial poderia consumir muito tempo. Na prática, compilação incremental, seleção, paralelismo e encerramento antecipado ajudam, mas o custo continua muito maior do que um único `cargo test`.

Reduza o tempo com prioridades claras:

1. **mantenha testes unitários rápidos** para lógica pura;
2. **separe testes lentos** que dependem de rede, container ou serviço externo;
3. **use cargo-nextest** no fluxo normal quando ele trouxer isolamento e paralelismo adequados;
4. **execute por pacote ou módulo** em workspaces grandes;
5. **rode seleção em PR e análise ampla em agenda**;
6. **use cache de compilação** de maneira consciente;
7. **corrija flakiness** antes de culpar a ferramenta;
8. **não aumente timeouts sem investigar**.

O [cargo-nextest](/blog/cargo-nextest-testes-rust-2026/) melhora a operação da suíte, mas confirme na documentação da versão do `cargo-mutants` como selecionar o runner e repassar argumentos. O [sccache](/blog/sccache-rust-cache-compilacao-ci-2026/) também pode ajudar em determinados pipelines, embora mutações alterem fontes e reduzam parte do reaproveitamento esperado.

## Estratégia de CI para projetos pequenos

Para uma biblioteca pequena e rápida, um job dedicado pode ser direto:

```yaml
name: mutation-tests

on:
  pull_request:
  push:
    branches: [main]

jobs:
  mutants:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: dtolnay/rust-toolchain@stable

      - name: Instalar cargo-mutants
        run: cargo install cargo-mutants --locked

      - name: Confirmar suíte base
        run: cargo test --all-features

      - name: Executar testes de mutação
        run: cargo mutants
```

A lógica é compatível com GitHub Actions, Gitea Actions e outros sistemas, mas uma esteira de produção deve melhorar este exemplo:

- fixe uma versão conhecida do `cargo-mutants`;
- use cache sem esconder mudanças de toolchain;
- preserve o relatório como artefato quando necessário;
- defina timeout do job inteiro;
- deixe claro se qualquer sobrevivente bloqueia o merge;
- valide o comando em um fork antes de torná-lo obrigatório.

Consulte o [guia de CI/CD em Rust](/artigos/ci-cd-rust/) para encaixar mutação junto de formato, Clippy, testes, cobertura e auditoria de dependências.

## Estratégia de CI para workspaces grandes

Rodar tudo em toda PR pode ser desperdício. Uma política em camadas costuma funcionar melhor:

| Momento | Escopo | Objetivo |
|---|---|---|
| Pull request | Crates ou arquivos alterados | Feedback rápido sobre a mudança |
| Merge em `main` | Pacotes críticos | Detectar lacunas antes de acumular |
| Job noturno | Workspace ampliado | Encontrar sobreviventes fora do diff |
| Pré-release | Bibliotecas públicas e regras críticas | Aumentar confiança no artefato |

Evite depender apenas do diff. Uma alteração em trait compartilhada, feature ou macro pode afetar crates que parecem não ter mudado. A seleção deve respeitar o grafo do workspace e o risco do componente.

Também não use uma pontuação global como único gate. Um pacote de cálculo financeiro interno e um binário utilitário podem merecer políticas diferentes. Mesmo fora de conteúdo financeiro, regras de autorização, integridade de dados e compatibilidade de API têm impacto muito maior do que formatação de uma mensagem auxiliar.

## Mutation score: use com cuidado

Algumas equipes calculam uma pontuação aproximada:

```text
mutantes capturados / mutantes viáveis
```

Ela ajuda a acompanhar tendência, mas pode ser manipulada ou interpretada mal. Um projeto pode aumentar a pontuação excluindo arquivos difíceis, escrevendo testes acoplados ou removendo código que a ferramenta consegue mutar, sem melhorar a experiência real do usuário.

Uma análise saudável pergunta:

- quais sobreviventes alteram contratos importantes?
- quais módulos concentram lacunas?
- a suíte melhorou nas fronteiras?
- exclusões possuem motivo revisável?
- o tempo de execução continua sustentável?
- regressões descobertas viraram testes claros?

Prefira uma lista pequena de sobreviventes entendidos a uma meta de “100%” obtida com exclusões opacas.

## Armadilhas comuns

### Rodar antes de estabilizar a suíte

Se `cargo test` falha aleatoriamente, cada mutante amplifica o ruído. Corrija testes flaky, dependências externas e vazamentos de estado primeiro.

### Tratar timeout como mutante capturado

Uma mudança que gera loop ou lentidão pode representar um defeito real, mas também pode expor timeout mal calibrado. Investigue separadamente; não some tudo como sucesso sem entender.

### Criar um teste por linha

O objetivo é proteger comportamento. Um bom teste de propriedade ou de fronteira pode capturar vários mutantes mantendo liberdade de refactor.

### Excluir todo código difícil

Dificuldade de teste frequentemente indica acoplamento, efeitos colaterais escondidos ou responsabilidades misturadas. Use o relatório como sinal arquitetural antes de adicionar uma exclusão.

### Rodar somente localmente

Uma ferramenta que depende da disciplina individual tende a desaparecer. Mesmo que a execução completa seja agendada, mantenha ao menos um comando reproduzível e uma política no repositório.

### Confiar em sobreviventes sem ler o diff

O mutante pode ser equivalente, não compilável em outra feature ou irrelevante para o produto. Triagem humana continua necessária.

## Checklist de adoção

- [ ] `cargo test` passa de forma determinística;
- [ ] o projeto possui testes de fronteira para regras críticas;
- [ ] `cargo-mutants` está instalado com versão controlada;
- [ ] a primeira execução usa uma crate ou módulo pequeno;
- [ ] sobreviventes são classificados antes de criar exclusões;
- [ ] novos testes observam contratos públicos;
- [ ] código gerado e integrações externas têm política explícita;
- [ ] a CI confirma a suíte base antes da mutação;
- [ ] PRs recebem um escopo rápido e jobs agendados cobrem mais;
- [ ] relatórios relevantes ficam disponíveis para revisão;
- [ ] timeout e flakiness são monitorados;
- [ ] cobertura, mutação, proptest e fuzzing têm papéis diferentes;
- [ ] nenhuma meta numérica incentiva testes artificiais.

## Testes de mutação para carreira e portfólio Rust

Em um projeto de portfólio, `cargo-mutants` demonstra uma maturidade rara: você não está apenas exibindo endpoints, mas mostrando como verifica a qualidade da verificação. Um README forte pode documentar:

- comando da suíte normal;
- cobertura com `cargo-llvm-cov`;
- escopo da análise de mutação;
- um sobrevivente real encontrado;
- o teste de fronteira criado para capturá-lo;
- tempo do job e decisões de CI.

Isso rende uma conversa de entrevista melhor do que citar uma porcentagem isolada. Você consegue explicar por que uma linha coberta não era suficiente, como evitou acoplamento à implementação e por que deixou um mutante equivalente documentado.

A habilidade é relevante para [vagas Rust](/vagas/) em backend, sistemas, fintechs, infraestrutura e segurança, além de ajudar na avaliação de [empresas que usam Rust](/empresas/). Times que adotam Rust para confiabilidade precisam de pessoas capazes de testar regras e fronteiras que o sistema de tipos não consegue provar sozinho.

Para comparar culturas de teste, vale conhecer também o ecossistema do <a href="https://golang.com.br/" target="_blank" rel="noopener noreferrer" onclick="umami.track('portfolio-site-click', { destination: 'golang.com.br' })">Golang Brasil</a>. A comparação entre ferramentas importa menos do que a pergunta comum às duas linguagens: a suíte falha quando o comportamento importante muda?

## Perguntas frequentes

### O que é cargo-mutants?

É uma ferramenta que altera temporariamente o código Rust, executa os testes e registra se a suíte detectou cada mudança. Ela é usada para avaliar a força das asserções e encontrar comportamentos sem proteção adequada.

### Como começar?

```bash
cargo install cargo-mutants --locked
cargo test
cargo mutants
```

Faça a primeira análise em escopo pequeno e consulte `cargo mutants --help` para os filtros suportados pela versão instalada.

### Ele substitui cobertura?

Não. Cobertura mostra execução; mutação mostra sensibilidade a alterações. Use as duas para responder perguntas complementares.

### Todo sobrevivente exige um novo teste?

Não. Primeiro verifique se a mudança afeta o contrato, se é equivalente, se o código pertence ao escopo e se existe uma forma estável de observar o comportamento. Alguns sobreviventes pedem teste; outros pedem refactor, remoção de código redundante ou exclusão documentada.

### Precisa rodar em toda pull request?

Não necessariamente. Projetos pequenos podem executar tudo. Workspaces grandes podem analisar pacotes alterados na PR e usar jobs noturnos ou pré-release para cobertura mais ampla.

## Conclusão

`cargo-mutants` ajuda a responder uma pergunta que cobertura e contagem de testes não resolvem: **a suíte percebe um defeito plausível no comportamento?** Ao introduzir alterações controladas e observar quais sobrevivem, a ferramenta revela asserções fracas, fronteiras esquecidas, código difícil de testar e contratos que existem apenas na cabeça da equipe.

Comece pequeno: escolha uma função importante, rode os mutantes, leia cada sobrevivente e escreva testes sobre o comportamento público. Depois transforme o aprendizado em um job proporcional ao tamanho do repositório — rápido na pull request, mais amplo em agenda e rigoroso antes de releases críticos.

Para construir uma estratégia completa, combine testes de mutação com [testes em Rust](/tutoriais/testes-rust/), [cargo-nextest](/blog/cargo-nextest-testes-rust-2026/), [cobertura com cargo-llvm-cov](/blog/cargo-llvm-cov-cobertura-testes-rust-ci-2026/), [proptest e fuzzing](/blog/rust-proptest-fuzzing-property-based-testing-2026/) e [CI/CD](/artigos/ci-cd-rust/). O resultado não é apenas uma barra verde: é uma suíte capaz de explicar quais mudanças perigosas ela realmente impede.
