Cobertura de código com o Bazel

O Bazel inclui um subcomando coverage para produzir relatórios de cobertura de código em repositórios que podem ser testados com bazel coverage. Devido às idiossincrasias dos vários ecossistemas de idiomas, nem sempre é trivial fazer esse trabalho para um determinado projeto.

Esta página documenta o processo geral de criação e visualização de relatórios de cobertura e também apresenta algumas notas específicas para idiomas com configurações conhecidas. A primeira opção é ler a seção geral e, em seguida, ler os requisitos de uma linguagem específica. Observe também a seção de execução remota, que requer algumas considerações adicionais.

Embora seja possível fazer muitas personalizações, este documento se concentra na produção e no consumo de relatórios lcov, que atualmente são a rota mais compatível.

Criar um relatório de cobertura

preparação

O fluxo de trabalho básico para criar relatórios de cobertura requer o seguinte:

  • Um repositório básico com destinos de teste
  • Um conjunto de ferramentas com as ferramentas de cobertura de código específicas da linguagem instaladas
  • Uma configuração de "instrumentação&correção correta

Os dois primeiros são específicos da linguagem e, principalmente, simples, mas o último pode ser mais difícil para projetos complexos.

"Instrumentação" neste caso se refere às ferramentas de cobertura usadas para um alvo específico. O Bazel permite ativar isso para um subconjunto específico de arquivos usando a sinalização --instrumentation_filter, que especifica um filtro para destinos testados com a instrumentação ativada. Para ativar a instrumentação para testes, a sinalização --instrument_test_targets é necessária.

Por padrão, o Bazel tenta corresponder os pacotes de destino e exibe o filtro relevante como uma mensagem INFO.

Cobertura em funcionamento

Para produzir um relatório de cobertura, use bazel coverage --combined_report=lcov [target]. Isso executa os testes do destino, gerando relatórios de cobertura no formato lcov para cada arquivo.

Depois de concluído, o Bazel executa uma ação que coleta todos os arquivos de cobertura produzidos e os mescla em um, que é criado em $(bazel info output_path)/_coverage/_coverage_report.dat.

Os relatórios de cobertura também são produzidos se os testes falharem. No entanto, isso não se estende aos testes com falha. Somente os testes aprovados são informados.

Ver cobertura

O relatório de cobertura é gerado somente no formato lcov não legível. Com isso, podemos usar o utilitário genhtml (parte do projeto lcov) para produzir um relatório que pode ser visualizado em um navegador da Web:

genhtml --output genhtml "$(bazel info output_path)/_coverage/_coverage_report.dat"

Observe que genhtml também lê o código-fonte para anotar a cobertura ausente nesses arquivos. Para que isso funcione, espera-se que genhtml seja executado na raiz do projeto bazel.

Para ver o resultado, basta abrir o arquivo index.html produzido no diretório genhtml em qualquer navegador da Web.

Para mais ajuda e informações sobre a ferramenta genhtml ou o formato de cobertura lcov, consulte o projeto lcov.

Execução remota

No momento, a execução com teste remoto tem algumas ressalvas:

  • A ação de combinação de relatórios ainda não pode ser executada remotamente. Isso ocorre porque o Bazel não considera os arquivos de saída de cobertura como parte do gráfico (consulte este problema) e, portanto, não pode tratá-los corretamente como entradas para a ação de combinação. Para resolver isso, use --strategy=CoverageReport=local.
    • Observação: pode ser necessário especificar algo como --strategy=CoverageReport=local,remote, caso o Bazel esteja configurado para testar local,remote, devido à forma como o Bazel resolve estratégias.
  • --remote_download_minimal e sinalizações semelhantes também não podem ser usadas como consequência do primeiro.
  • No momento, o Bazel não criará informações de cobertura se os testes tiverem sido armazenados em cache anteriormente. Para contornar isso, --nocache_test_results pode ser definido especificamente para execuções de cobertura, embora seja claro que isso gera um alto custo em termos de tempos de teste.
  • --experimental_split_coverage_postprocessing e --experimental_fetch_all_coverage_outputs
    • Normalmente, a cobertura é executada como parte da ação de teste e, por isso, por padrão, não obtemos toda a cobertura como saídas da execução remota por padrão. Essas sinalizações substituem o padrão e recebem os dados de cobertura. Consulte este problema para ver mais detalhes.

Configuração específica do idioma

Java

O Java funciona imediatamente com a configuração padrão. Os conjuntos de ferramentas do bazel contêm tudo o que é necessário para a execução remota, incluindo JUnit.

Python

Prerequisites

A execução da cobertura com Python tem alguns pré-requisitos:

Consumir a cobertura.py modificada

Uma maneira de fazer isso é via rules_python, que fornece a capacidade de usar um arquivo requirements.txt. Em seguida, os requisitos listados no arquivo são criados como destinos do Bazel usando a regra de repositório pip_install (link em inglês).

A requirements.txt precisa ter a seguinte entrada:

git+https://github.com/ulfjack/coveragepy.git@lcov-support

Os arquivos rules_python, pip_install e requirements.txt precisam ser usados no arquivo WORKSPACE da seguinte forma:

load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")

http_archive(
    name = "rules_python",
    url = "https://github.com/bazelbuild/rules_python/releases/download/0.5.0/rules_python-0.5.0.tar.gz",
    sha256 = "cd6730ed53a002c56ce4e2f396ba3b3be262fd7cb68339f0377a45e8227fe332",
)

load("@rules_python//python:pip.bzl", "pip_install")

pip_install(
   name = "python_deps",
   requirements = "//:requirements.txt",
)

Em seguida, o requisito"cover.py"pode ser consumido pelos destinos de teste definindo o seguinte nos arquivos BUILD:

load("@python_deps//:requirements.bzl", "entry_point")

alias(
    name = "python_coverage_tools",
    actual = entry_point("coverage"),
)

py_test(
    name = "test",
    srcs = ["test.py"],
    env = {
        "PYTHON_COVERAGE": "$(location :python_coverage_tools)",
    },
    deps = [
        ":main",
        ":python_coverage_tools",
    ],
)