cargo-llvm-cov: Cobertura de Testes Rust na CI | Rust Brasil

Meça cobertura de testes em Rust com cargo-llvm-cov. Guia com HTML, LCOV, workspaces, features, nextest, exclusões, CI e interpretação dos resultados.

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

ObjetivoComando
Instalar suporte do LLVMrustup component add llvm-tools-preview
Instalar a ferramentacargo install cargo-llvm-cov --locked
Ver resumo no terminalcargo llvm-cov
Gerar relatório HTMLcargo llvm-cov --html
Gerar e abrir o HTMLcargo llvm-cov --open
Cobrir workspace e featurescargo llvm-cov --workspace --all-features
Gerar LCOVcargo llvm-cov --lcov --output-path lcov.info
Gerar Cobertura XMLcargo llvm-cov --cobertura --output-path cobertura.xml
Executar com nextestcargo llvm-cov nextest
Limpar dados anteriorescargo 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:

  1. tratamento de erros nunca executado;
  2. branches de autorização, validação e timeout;
  3. conversões de tipos em fronteiras externas;
  4. código de retry, rollback ou cleanup;
  5. parsers com formatos inválidos;
  6. estados de concorrência e cancelamento;
  7. 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:

JobComandoObjetivo
Feedback rápidocargo nextest runInformar falhas cedo
Coberturacargo llvm-cov nextest --workspaceGerar métrica e artefato
Testes especiaiscomandos dedicadosDoctests, 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:

  1. por que esse código não entra na métrica;
  2. como ele é validado de outra forma;
  3. 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:

  1. gere a baseline atual sem bloquear merges;
  2. remova arquivos gerados da métrica com justificativa;
  3. identifique módulos críticos e lacunas reais;
  4. adicione testes de regressão durante algumas semanas;
  5. estabeleça um limite ligeiramente abaixo da baseline estável;
  6. 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-preview e cargo-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-nextest se 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.