Para medir cobertura de testes em Rust, instale llvm-tools-preview e cargo-llvm-cov, rode cargo llvm-cov --html e examine quais linhas, regiões e funções importantes continuam sem teste. Na CI, gere LCOV ou Cobertura XML, publique o artefato e aplique um limite apenas depois de estabelecer uma baseline realista para o projeto.
O cargo-llvm-cov usa a instrumentação de cobertura do compilador e as ferramentas do LLVM. Ele funciona no ecossistema Cargo, entende bibliotecas, binários, testes de integração, features e workspaces, além de conversar com formatos aceitos por plataformas de qualidade. Isso o torna uma escolha prática para responder a uma pergunta que cargo test sozinho não resolve: quais caminhos do código foram realmente exercitados?
Este guia mostra a instalação, os relatórios HTML, LCOV e Cobertura, a integração com cargo-nextest, o uso em workspaces, as exclusões legítimas, a configuração de CI e — principalmente — como interpretar cobertura sem transformar a porcentagem em meta vazia.
Resposta rápida: comandos essenciais
| Objetivo | Comando |
|---|---|
| Instalar suporte do LLVM | rustup component add llvm-tools-preview |
| Instalar a ferramenta | cargo install cargo-llvm-cov --locked |
| Ver resumo no terminal | cargo llvm-cov |
| Gerar relatório HTML | cargo llvm-cov --html |
| Gerar e abrir o HTML | cargo llvm-cov --open |
| Cobrir workspace e features | cargo llvm-cov --workspace --all-features |
| Gerar LCOV | cargo llvm-cov --lcov --output-path lcov.info |
| Gerar Cobertura XML | cargo llvm-cov --cobertura --output-path cobertura.xml |
| Executar com nextest | cargo llvm-cov nextest |
| Limpar dados anteriores | cargo llvm-cov clean --workspace |
Consulte cargo llvm-cov --help na versão instalada antes de copiar uma configuração para um pipeline crítico. Flags e recursos podem evoluir; fixar a versão da ferramenta na CI melhora a reprodutibilidade.
O que cobertura de testes mede
Cobertura registra quais partes do programa foram visitadas durante uma execução instrumentada. Dependendo do relatório, você verá métricas como:
- linhas: linhas de código executadas ao menos uma vez;
- regiões: trechos identificados pela instrumentação do compilador;
- funções: funções chamadas durante os testes;
- branches: alternativas de controle exercitadas, quando disponíveis no fluxo usado;
- arquivos: distribuição da cobertura por módulo ou crate.
Uma linha coberta não significa que o comportamento foi validado corretamente. Este teste cobre a função, mas não prova quase nada:
fn dividir(total: u64, partes: u64) -> Result<u64, &'static str> {
if partes == 0 {
return Err("divisão por zero");
}
Ok(total / partes)
}
#[test]
fn executa_divisao() {
let _ = dividir(10, 2);
}
O caminho feliz foi executado, porém o teste não possui uma asserção e não cobre o erro. Uma versão melhor verifica os dois contratos:
#[test]
fn divide_valores_validos() {
assert_eq!(dividir(10, 2), Ok(5));
}
#[test]
fn rejeita_zero_partes() {
assert_eq!(dividir(10, 0), Err("divisão por zero"));
}
Portanto, cobertura é um mapa de lacunas, não um selo de correção. Combine-a com testes unitários, integração, property-based testing e análise das invariantes do domínio. O guia de proptest e fuzzing em Rust mostra como explorar entradas que exemplos manuais não antecipam.
Instalando cargo-llvm-cov
Primeiro, confirme que o projeto usa uma toolchain gerenciada pelo rustup:
rustc --version
cargo --version
rustup show active-toolchain
Instale as ferramentas de cobertura distribuídas como componente do toolchain:
rustup component add llvm-tools-preview
Depois instale o subcomando Cargo:
cargo install cargo-llvm-cov --locked
cargo llvm-cov --version
O --locked pede que a instalação respeite o lockfile publicado pela ferramenta. Em CI, você pode fixar uma versão para evitar que uma release nova altere comportamento sem revisão:
cargo install cargo-llvm-cov --version X.Y.Z --locked
Substitua X.Y.Z pela versão homologada no repositório. Ferramentas como cargo-msrv tratam a compatibilidade do projeto com compiladores antigos; o versionamento de ferramentas de CI resolve outro problema: tornar a automação previsível.
Primeiro relatório no terminal
Na raiz do projeto:
cargo llvm-cov
A ferramenta configura as flags de instrumentação, compila os targets necessários, executa os testes e imprime um resumo. O primeiro build é mais lento porque os artefatos instrumentados diferem de um build normal.
Se você mudou configurações ou suspeita de dados antigos, limpe a cobertura:
cargo llvm-cov clean --workspace
cargo llvm-cov
Evite comparar o tempo desse build com o tempo normal de produção. Instrumentação adiciona trabalho ao compilador e muda os artefatos. Para investigar tempo de compilação, use as técnicas do guia de otimização de build em Rust e ferramentas como sccache.
Relatório HTML: onde a cobertura vira ação
O resumo informa que um crate tem, por exemplo, 78% de linhas cobertas. O relatório HTML mostra quais linhas ficaram de fora:
cargo llvm-cov --html
Para abrir automaticamente:
cargo llvm-cov --open
No relatório, procure primeiro por:
- tratamento de erros nunca executado;
- branches de autorização, validação e timeout;
- conversões de tipos em fronteiras externas;
- código de retry, rollback ou cleanup;
- parsers com formatos inválidos;
- estados de concorrência e cancelamento;
- bugs corrigidos sem teste de regressão.
Não comece pelos arquivos com menor porcentagem. Um binário de exemplo com 20% pode ser irrelevante, enquanto uma função de autorização com 95% pode esconder justamente o branch mais perigoso.
Exemplo: lendo uma lacuna
Considere uma função que classifica respostas de uma API:
#[derive(Debug, PartialEq)]
enum Acao {
Sucesso,
TentarNovamente,
FalhaPermanente,
}
fn classificar(status: u16) -> Acao {
match status {
200..=299 => Acao::Sucesso,
408 | 429 | 500..=599 => Acao::TentarNovamente,
_ => Acao::FalhaPermanente,
}
}
Se os testes cobrem apenas 200 e 404, a linha do retry pode aparecer descoberta. O teste que falta não deve existir “para subir a porcentagem”; ele documenta uma decisão operacional:
#[test]
fn permite_retry_em_rate_limit_e_erro_do_servidor() {
assert_eq!(classificar(429), Acao::TentarNovamente);
assert_eq!(classificar(503), Acao::TentarNovamente);
}
Esse é o melhor uso de cobertura: revelar uma regra importante que estava implícita.
LCOV, Cobertura XML e JSON
O HTML é ótimo para pessoas. Plataformas de CI e serviços de análise normalmente consomem formatos de máquina.
LCOV
cargo llvm-cov --lcov --output-path lcov.info
LCOV é aceito por várias plataformas de cobertura e extensões de editor. O arquivo lcov.info deve ser tratado como artefato gerado, não como conteúdo para versionar no Git, salvo se o projeto tiver uma razão específica.
Cobertura XML
cargo llvm-cov --cobertura --output-path cobertura.xml
O formato Cobertura XML aparece em integrações com sistemas de CI, dashboards e ferramentas que exibem cobertura por arquivo ou pull request.
JSON
cargo llvm-cov --json --output-path cobertura.json
JSON é útil quando a equipe precisa de uma validação própria, como comparar apenas crates publicadas ou calcular uma métrica de um conjunto selecionado de diretórios.
Escolha um formato por consumidor. Gerar HTML, LCOV, XML e JSON em toda pull request sem que ninguém use os arquivos só aumenta tempo e armazenamento.
Workspaces, pacotes, features e targets
Em um projeto simples, cargo llvm-cov costuma bastar. Em workspaces, a cobertura depende de uma decisão explícita sobre escopo.
Workspace inteiro
cargo llvm-cov --workspace
Todas as features
cargo llvm-cov --workspace --all-features
Todos os targets relevantes
cargo llvm-cov --workspace --all-targets
Um pacote específico
cargo llvm-cov -p dominio
Não aplique --all-features automaticamente quando o projeto possui backends mutuamente exclusivos. Uma biblioteca pode oferecer rustls e native-tls, por exemplo, sem suportar as duas features juntas. Nesse caso, use uma matriz:
--no-default-features
--features rustls
--no-default-features --features native-tls
O mesmo raciocínio vale para targets. Código específico de Windows não será coberto em um runner Linux. Você pode manter jobs por sistema operacional ou declarar honestamente que a métrica principal cobre apenas o target de produção.
Para entender como features se combinam, consulte o guia de compilação condicional em Rust. Para organizar vários crates sem esconder o contrato entre eles, veja Cargo workspaces e monorepos.
cargo-llvm-cov com cargo-nextest
O cargo-nextest executa testes com melhor paralelismo, perfis, retries e particionamento. O cargo-llvm-cov possui integração para usar esse runner mantendo a instrumentação:
cargo llvm-cov nextest
Em um workspace:
cargo llvm-cov nextest --workspace --all-features
Essa combinação é útil em repositórios com suíte grande. Antes de adotá-la, compare a seleção de testes com o comando já usado pela equipe. Um perfil do nextest que ignora testes lentos ou marca casos como flaky pode produzir cobertura diferente do cargo test completo.
Uma estratégia comum separa responsabilidades:
| Job | Comando | Objetivo |
|---|---|---|
| Feedback rápido | cargo nextest run | Informar falhas cedo |
| Cobertura | cargo llvm-cov nextest --workspace | Gerar métrica e artefato |
| Testes especiais | comandos dedicados | Doctests, integração externa ou targets específicos |
Não duplique toda a suíte sem medir o custo. Em alguns projetos, o próprio job de cobertura já pode cumprir a validação principal; em outros, é melhor manter um job rápido independente.
Excluindo arquivos sem maquiar o resultado
Projetos reais contêm código que não deve dominar a métrica:
- arquivos gerados;
- bindings produzidos por ferramentas;
- migrations incorporadas;
- exemplos didáticos;
- binários administrativos;
- código específico de um target testado em outro runner;
- adaptadores triviais sem lógica de domínio.
O cargo-llvm-cov permite ignorar arquivos por expressão regular. Um exemplo conceitual:
cargo llvm-cov \
--workspace \
--ignore-filename-regex '(^|/)(generated|migrations|examples)/'
Teste a expressão no repositório e confira a lista final. Uma regex ampla pode esconder src/generated_policy.rs junto com arquivos realmente gerados.
Uma exclusão legítima deve responder a três perguntas no README ou na configuração da CI:
- por que esse código não entra na métrica;
- como ele é validado de outra forma;
- quem revisa a exceção quando a estrutura muda.
Excluir src/ inteiro para atingir 90% obviamente destrói o valor da ferramenta. Mais sutil — e igualmente ruim — é excluir módulos difíceis apenas porque reduziram a porcentagem.
Cobertura em GitHub Actions
Um workflow básico pode gerar LCOV e guardar o relatório HTML como artefato:
name: coverage
on:
pull_request:
push:
branches: [main]
jobs:
llvm-cov:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Instalar Rust e ferramentas LLVM
uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- name: Instalar cargo-llvm-cov
run: cargo install cargo-llvm-cov --locked
- name: Gerar LCOV
run: cargo llvm-cov --workspace --all-features --lcov --output-path lcov.info
- name: Gerar HTML
run: cargo llvm-cov --workspace --all-features --html
- name: Publicar artefatos
uses: actions/upload-artifact@v4
with:
name: cobertura-rust
path: |
lcov.info
target/llvm-cov/html/
Ajuste o caminho do HTML conforme a saída informada pela versão instalada. Antes de exigir --all-features, confirme que essa combinação é suportada.
Em Gitea Actions, a estrutura é semelhante quando o runner oferece compatibilidade com essas actions. Se não houver action de upload disponível, salve o relatório pelo mecanismo de artefatos adotado pela instalação. O guia de CI/CD em Rust ajuda a encaixar cobertura com fmt, Clippy, testes e auditoria de dependências.
Como definir um limite mínimo
Ferramentas de cobertura conseguem falhar o job quando o resultado fica abaixo de um limite. A pergunta mais difícil não é “como configurar?”, e sim “qual limite representa qualidade para este projeto?”.
Um processo saudável:
- gere a baseline atual sem bloquear merges;
- remova arquivos gerados da métrica com justificativa;
- identifique módulos críticos e lacunas reais;
- adicione testes de regressão durante algumas semanas;
- estabeleça um limite ligeiramente abaixo da baseline estável;
- aumente o limite gradualmente quando isso acompanhar testes úteis.
Exemplo: se a cobertura oscila entre 73% e 76%, começar com um gate de 70% impede uma queda grande sem forçar testes artificiais. Um gate de 90% criado de um dia para o outro tende a gerar asserts irrelevantes, exclusões agressivas ou testes acoplados à implementação.
Também vale separar metas:
- biblioteca de regras de negócio: cobertura alta e foco em branches;
- adaptador HTTP: integração dos contratos importantes;
- CLI pequena: testes end-to-end dos comandos;
- bindings gerados: excluídos, mas validados pelo gerador e por testes de fronteira;
unsafe: testes, Miri e revisão especializada, não apenas porcentagem.
Para código unsafe, use também o guia de Miri e undefined behavior em Rust. Cobertura informa que uma linha executou; Miri procura determinadas violações do modelo de memória durante a execução interpretada. As ferramentas respondem a perguntas diferentes.
Erros comuns com cargo-llvm-cov
Confundir cobertura alta com testes bons
Um teste pode executar muitas linhas sem verificar resultados. Revise asserts, propriedades e falhas esperadas, não apenas o dashboard.
Medir só o caminho feliz
APIs, parsers, filas e serviços falham. Dê prioridade a timeout, cancelamento, retry, entrada inválida, permissões e indisponibilidade de dependências.
Misturar dados de execuções antigas
Quando o relatório parece incoerente, limpe os dados:
cargo llvm-cov clean --workspace
Depois gere novamente com a mesma toolchain, features e targets.
Usar uma matriz diferente da produção
Cobrir features que ninguém usa e ignorar o backend de produção produz um número bonito e pouco útil. Documente a combinação canônica.
Exigir 100%
Alguns caminhos são defensivos, dependem do sistema operacional ou representam falhas difíceis de simular. Testá-los pode valer a pena, mas 100% não deve ser um dogma. O custo de manutenção também conta.
Publicar o relatório com dados sensíveis
Nomes de arquivos, caminhos, código-fonte renderizado e estrutura interna podem aparecer em artefatos. Em repositórios privados, mantenha relatórios com acesso compatível e revise integrações externas antes do upload.
Rodar cobertura em toda combinação possível
Uma matriz de três sistemas, quatro feature sets e vários targets pode multiplicar a conta da CI. Comece pela configuração de produção e adicione variações quando elas cobrem contratos reais.
Checklist de adoção
- instalar
llvm-tools-previewecargo-llvm-cov; - gerar um relatório HTML local;
- identificar a combinação canônica de features e targets;
- decidir se o escopo é pacote ou workspace;
- excluir somente código gerado ou irrelevante, com justificativa;
- verificar tratamento de erros e branches críticos;
- integrar
cargo-nextestse ele já for o runner do projeto; - escolher LCOV, Cobertura ou JSON conforme o consumidor;
- publicar o relatório como artefato protegido;
- registrar a baseline antes de criar um gate;
- aplicar um limite que evite regressão sem incentivar testes vazios;
- revisar a métrica quando features, targets ou crates mudarem.
cargo-llvm-cov em projetos de portfólio e carreira
Cobertura agrega valor ao portfólio quando conta uma história de engenharia. Em vez de colocar apenas um badge “85%”, explique no README:
- qual comando reproduz o relatório;
- quais features entram na medição;
- por que certos arquivos foram excluídos;
- quais módulos críticos possuem testes de erro;
- como a CI impede uma regressão relevante.
Um projeto Axum, por exemplo, pode testar regras de domínio sem servidor, handlers com requests controladas e persistência com uma base isolada. Combine esse desenho com o guia de Axum em Rust, SQLx e testes em Rust. O resultado demonstra muito mais maturidade do que uma API CRUD sem estratégia de validação.
Em entrevistas, uma resposta forte para “como você avalia a qualidade dos testes?” não se limita à porcentagem. Ela menciona cobertura como detector de lacunas, testes de regressão, branches de erro, property-based testing, estabilidade da suíte, tempo de feedback e revisão do que foi excluído.
Esse repertório é relevante para backend, infraestrutura, sistemas embarcados e manutenção de bibliotecas. Consulte as vagas Rust, o diretório de empresas que usam Rust e o plano de estudos sênior para conectar ferramentas de qualidade ao desenvolvimento profissional.
Perguntas frequentes
Como medir cobertura de testes em Rust?
Instale llvm-tools-preview com rustup component add llvm-tools-preview, instale cargo-llvm-cov e execute cargo llvm-cov. Para investigar os arquivos visualmente, use cargo llvm-cov --html ou cargo llvm-cov --open.
Como gerar relatório HTML?
Use cargo llvm-cov --html. A opção --open gera e abre o relatório. Navegue pelos módulos e procure branches de erro, validações e regressões importantes, em vez de olhar somente o total.
Funciona com workspaces?
Sim. Use --workspace, selecione pacotes com -p e combine features e targets conforme o contrato do repositório. Não use --all-features se as features forem mutuamente exclusivas.
Funciona com cargo-nextest?
Sim. Execute cargo llvm-cov nextest. Confirme que o perfil do nextest executa a mesma seleção de testes que você deseja representar no relatório.
Qual porcentagem de cobertura é boa?
Não existe um valor universal. Muitos times começam com uma baseline e um gate entre 70% e 85%, mas a qualidade depende do que está coberto. Um branch crítico de autorização sem teste é mais importante do que dezenas de getters cobertos.
Conclusão
O cargo-llvm-cov transforma cobertura em uma parte reproduzível do fluxo Cargo. Ele instrumenta o projeto com suporte do LLVM, executa a suíte e produz relatórios que funcionam tanto para pessoas — HTML — quanto para automações — LCOV, Cobertura e JSON.
O melhor fluxo é: gere o HTML localmente, defina o escopo real de features e targets, investigue caminhos críticos descobertos, publique um artefato na CI e só então crie um limite mínimo baseado na baseline. Cobertura deve orientar perguntas melhores sobre os testes, não substituir a análise de comportamento.
Para continuar, combine este guia com cargo-nextest, Criterion para benchmarks, proptest e fuzzing e Miri para código unsafe. Juntas, essas ferramentas cobrem velocidade de feedback, regressões funcionais, performance e segurança de memória — sem reduzir qualidade a um único número.