테스트 커버리지 시각화

Tier: Free, Premium, Ultimate Offering: GitLab.com, Self-Managed, GitLab Dedicated

GitLab CI/CD를 사용하여 즐겨 사용하는 테스트 또는 커버리지 분석 도구의 테스트 커버리지 정보를 수집하고, MR(Merge Request)의 파일 차이 보기 내에서 이 정보를 시각화할 수 있습니다. 이를 통해 MR이 Merge되기 전에 테스트에서 확인된 라인과 테스트가 아직 필요한 라인을 볼 수 있습니다.

테스트 커버리지 시각화 파일 차이 보기

테스트 커버리지 시각화 작동 방식

커버리지 정보를 수집하는 작업은 GitLab CI/CD artifacts reports 기능을 이용하여 수행됩니다. 수집할 커버리지 보고서를 하나 이상 지정할 수 있으며 와일드카드 경로를 포함할 수 있습니다. GitLab은 모든 파일의 커버리지 정보를 가져와서 결합합니다. 커버리지 파일은 백그라운드 작업에서 구문 분석되므로 파이프라인 완료 후 시각화가 페이지에 로딩될 때까지 지연될 수 있습니다.

커버리지 분석이 작동하려면 적절한 형식의 Cobertura XML 보고서를 artifacts:reports:coverage_report에 제공해야 합니다. 이 형식은 원래 Java용으로 개발되었지만, 다른 언어의 커버리지 분석 프레임워크에도 지원을 추가하는 플러그인이 있습니다. 예를 들어:

다른 커버리지 분석 프레임워크는 이 형식을 기본적으로 지원하며, 예를 들어 다음과 같습니다:

구성을 마치고 만일 MR이 커버리지 보고서를 수집하는 파이프라인을 트리거한다면, 커버리지 정보가 차이 보기에 표시됩니다. 이는 파이프라인의 어느 스테이지의 어떤 작업에서든 보고서가 됩니다. 각 라인에 대한 커버리지는 다음과 같이 표시됩니다:

  • covered (녹색): 최소한 한 번 이상 테스트에서 확인된 라인
  • no test coverage (주황색): 로드되었지만 실행되지 않은 라인
  • 커버리지 정보 없음: 인스트루먼트되지 않거나 로드되지 않은 라인

커버리지 막대 위로 마우스를 올리면 라인이 테스트에서 확인된 횟수와 같은 추가 정보를 얻을 수 있습니다.

테스트 커버리지 보고서를 업로드하더라도 다음과 같은 기능은 활성화되지 않습니다:

이를 따로 구성해야 합니다.

제약 사항

Cobertura 형식 XML 파일에는 100개의 <source> 노드에 대한 제한이 있습니다. 따라서 Cobertura 보고서가 100개의 노드를 초과하면 MR의 차이 보기에서 불일치 또는 일치하지 않을 수 있습니다.

단일 Cobertura XML 파일의 크기는 10 MiB를 초과할 수 없습니다. 큰 프로젝트의 경우 Cobertura XML을 여러 파일로 나누어야 합니다. 자세한 내용은 이 이슈를 참조하십시오. 여러 파일을 제출하는 경우 커버리지가 MR에 표시되기까지 몇 분이 걸릴 수 있습니다.

시각화는 파이프라인이 완료된 후에만 표시됩니다. 만약 파이프라인에 작업 매뉴얼 작업이 있다면, 파이프라인은 계속 진행되기 전에 매뉴얼 작업을 기다리며 완료로 간주되지 않습니다. 블로킹 매뉴얼 작업이 실행되지 않은 경우 시각화가 표시되지 않습니다.

데이터 만료

기본적으로 MR의 시각화를 그리는 데 사용된 데이터는 생성 후 1주일 후에 만료됩니다.

자식 파이프라인에서의 커버리지 보고서

만일 자식 파이프라인의 작업에서 커버리지 보고서를 생성하면 해당 보고서는 부모 파이프라인의 커버리지 보고서에 포함됩니다.

child_test_pipeline:
  trigger:
    include:
      - local: path/to/child_pipeline_with_coverage.yml

자동 클래스 경로 교정

커버리지 보고서는 filename이 프로젝트 루트에 대한 전체 경로를 포함하는 경우에만 변경된 파일과 일치합니다. 하지만 일부 커버리지 분석 프레임워크에서 생성된 Cobertura XML은 filename 경로가 클래스 패키지 디렉터리에 대한 상대 경로를 포함합니다.

프로젝트 루트의 상대적인 전체 경로를 찾기 위해 Cobertura XML 파서는 다음을 시도합니다:

  • sources 요소에서 source 경로를 추출하여 클래스 filename 경로와 결합합니다.
  • 후보 경로가 프로젝트에 존재하는지 확인합니다.
  • 일치하는 경로 중 첫 번째 후보를 클래스 전체 경로로 사용합니다.

경로 교정 예시

예를 들어, 다음과 같은 C# 프로젝트가 있습니다:

  • test-org/test-cs-project의 전체 경로
  • 프로젝트 루트에 대한 다음과 같은 파일:

    Auth/User.cs
    Lib/Utils/User.cs
    
  • Cobertura XML의 sources:

    <sources>
      <source>/builds/test-org/test-cs-project/Auth</source>
      <source>/builds/test-org/test-cs-project/Lib/Utils</source>
    </sources>
    

파서:

  • sources에서 AuthLib/Utils를 추출하여 프로젝트 루트를 기준으로한 클래스 경로를 결정합니다.
  • 추출된 sources와 클래스 filename을 결합합니다. 예를 들어, filename값이 User.cs인 경우, 파서는 파일 트리에서 일치하는 경로를 찾아 첫 번째 후보 경로인 Auth/User.cs를 가져옵니다.
  • class 요소에 대해 파일 트리에서 일치하는 경로를 찾기 위해 최대 100회 반복합니다. 이 한도에 도달하면 적합한 경로를 찾을 수 없는 경우 마지막 커버리지 보고서에는 클래스가 포함되지 않습니다.

자동 클래스 경로 교정은 다음과 같은 Java 프로젝트에서도 작동합니다:

  • test-org/test-java-project의 전체 경로
  • 프로젝트 루트에 대한 상대적인 파일:

    src/main/java/com/gitlab/security_products/tests/App.java
    
  • Cobertura XML의 sources:

    <sources>
      <source>/builds/test-org/test-java-project/src/main/java/</source>
    </sources>
    
  • class 요소의 filename 값이 com/gitlab/security_products/tests/App.java인 경우:

    <class name="com.gitlab.security_products.tests.App" filename="com/gitlab/security_products/tests/App.java" line-rate="0.0" branch-rate="0.0" complexity="6.0">
    
note
자동 클래스 경로 교정은 <CI_BUILDS_DIR>/<PROJECT_FULL_PATH>/... 형식의 source 경로에서만 작동합니다. 이 패턴을 따르지 않는 경우 source는 무시됩니다. 파서는 filename이 프로젝트 루트에 대한 전체 경로를 포함한다고 가정합니다.

테스트 커버리지 구성 예

이 섹션은 다른 프로그래밍 언어에 대한 테스트 커버리지 구성 예를 제공합니다. 또한 coverage-report 데모 프로젝트에서 실제 예제를 볼 수 있습니다.

JavaScript 예제

다음의 .gitlab-ci.yml 예제는 Mocha JavaScript 테스트 및 nyc 커버리지 도구를 사용하여 커버리지 아티팩트를 생성합니다:

test:
  script:
    - npm install
    - npx nyc --reporter cobertura mocha
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml

Java 및 Kotlin 예제

Maven 예제

다음의 Java 또는 Kotlin을 위한 .gitlab-ci.yml 예제는 Maven 를 사용하여 프로젝트를 빌드하고, 커버리지 도구로 JaCoCo를 사용하여 커버리지 아티팩트를 생성합니다. 자체 이미지를 빌드하려면 Docker 이미지 구성 및 스크립트를 확인하세요.

GitLab은 Cobertura 형식의 아티팩트를 예상하므로 업로드 전에 몇 가지 스크립트를 실행해야 합니다. test-jdk11 작업은 코드를 테스트하고 XML 아티팩트를 생성합니다. coverage-jdk-11 작업은 아티팩트를 Cobertura 보고서로 변환합니다:

test-jdk11:
  stage: test
  image: maven:3.6.3-jdk-11
  script:
    - mvn $MAVEN_CLI_OPTS clean org.jacoco:jacoco-maven-plugin:prepare-agent test jacoco:report
  artifacts:
    paths:
      - target/site/jacoco/jacoco.xml

coverage-jdk11:
  # test-jdk11의 단계 이후에 있어야 합니다.
  # 기본적으로 `visualize` 단계는 존재하지 않습니다.
  # 먼저 정의하거나 `deploy`와 같은 기존 단계를 선택하세요.
  stage: visualize
  image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.9
  script:
    # 프로젝트 경로를 사용하여 jacoco 보고서를 cobertura로 변환
    - python /opt/cover2cover.py target/site/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java/ > target/site/cobertura.xml
  needs: ["test-jdk11"]
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: target/site/cobertura.xml

Gradle 예제

다음의 Java 또는 Kotlin을 위한 .gitlab-ci.yml 예제는 Gradle 를 사용하여 프로젝트를 빌드하고, 커버리지 도구로 JaCoCo를 사용하여 커버리지 아티팩트를 생성합니다. 자체 이미지를 빌드하려면 Docker 이미지 구성 및 스크립트를 확인하세요.

GitLab은 Cobertura 형식의 아티팩트를 예상하므로 업로드 전에 몇 가지 스크립트를 실행해야 합니다. test-jdk11 작업은 코드를 테스트하고 XML 아티팩트를 생성합니다. coverage-jdk-11 작업은 아티팩트를 Cobertura 보고서로 변환합니다:

test-jdk11:
  stage: test
  image: gradle:6.6.1-jdk11
  script:
    - 'gradle test jacocoTestReport' # jacoco는 xml 보고서를 생성하도록 구성되어야 합니다.
  artifacts:
    paths:
      - build/jacoco/jacoco.xml

coverage-jdk11:
  # test-jdk11의 단계 이후에 있어야 합니다.
  # 기본적으로 `visualize` 단계는 존재하지 않습니다.
  # 먼저 정의하거나 `deploy`와 같은 기존 단계를 선택하세요.
  stage: visualize
  image: registry.gitlab.com/haynes/jacoco2cobertura:1.0.7
  script:
    # 프로젝트 경로를 사용하여 jacoco 보고서를 cobertura로 변환
    - python /opt/cover2cover.py build/jacoco/jacoco.xml $CI_PROJECT_DIR/src/main/java/ > build/cobertura.xml
  needs: ["test-jdk11"]
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: build/cobertura.xml

Python 예제

다음의 .gitlab-ci.yml 예제는 pytest-cov를 사용하여 테스트 커버리지 데이터를 수집합니다:

run tests:
  stage: test
  image: python:3
  script:
    - pip install pytest pytest-cov
    - pytest --cov --cov-report term --cov-report xml:coverage.xml
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

PHP 예제

PHP를 위한 다음의 .gitlab-ci.yml 예제는 PHPUnit 를 사용하여 테스트 커버리지 데이터를 수집하고 보고서를 생성합니다.

최소한의 phpunit.xml 파일을 사용하여 (참조 가능) 이 예제 리포지터리를 사용하여 테스트를 실행하고 coverage.xml을 생성할 수 있습니다:

run tests:
  stage: test
  image: php:latest
  variables:
    XDEBUG_MODE: coverage
  before_script:
    - apt-get update && apt-get -yq install git unzip zip libzip-dev zlib1g-dev
    - docker-php-ext-install zip
    - pecl install xdebug && docker-php-ext-enable xdebug
    - php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
    - php composer-setup.php --install-dir=/usr/local/bin --filename=composer
    - composer install
    - composer require --dev phpunit/phpunit phpunit/php-code-coverage
  script:
    - php ./vendor/bin/phpunit --coverage-text --coverage-cobertura=coverage.cobertura.xml
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.cobertura.xml

Codeception은 PHPUnit을 통해 run으로 Cobertura 보고서를 생성하는 것을 지원합니다. 생성된 파일의 경로는 --coverage-cobertura 옵션 및 paths 구성에 따라 unit test suite를 참조합니다. .gitlab-ci.yml을 구성하여 해당 경로에서 Cobertura를 찾을 수 있도록하세요.

C/C++ 예제

다음의 gcc 또는 g++ 컴파일러를 사용하는 C/C++를 위한 .gitlab-ci.yml 예제에서는 gcovr를 사용하여 Cobertura XML 형식의 커버리지 출력 파일을 생성합니다.

이 예제에서는 다음을 전제로 합니다. - Makefile은 이전 단계의 다른 작업 내에서 build 디렉터리에 의해 cmake에 의해 생성됩니다. (automake를 사용하여 Makefile을 생성하는 경우, make test 대신 make check를 호출해야 합니다.) - cmake (또는 automake)가 컴파일러 옵션 --coverage를 설정했습니다.

run tests:
  stage: test
  script:
    - cd build
    - make test
    - gcovr --xml-pretty --exclude-unreachable-branches --print-summary -o coverage.xml --root ${CI_PROJECT_DIR}
  artifacts:
    name: ${CI_JOB_NAME}-${CI_COMMIT_REF_NAME}-${CI_COMMIT_SHA}
    expire_in: 2 days
    reports:
      coverage_report:
        coverage_format: cobertura
        path: build/coverage.xml

Go 예시

다음은 Go에 대한 .gitlab-ci.yml 예시입니다.

  • 테스트를 실행하기 위해 go test를 사용합니다.
  • Go의 코드 커버리지 프로필을 Cobertura XML 형식으로 변환하기 위해 gocover-cobertura를 사용합니다.

이 예시는 Go 모듈이 사용되고 있다고 가정합니다. -covermode count 옵션은 -race 플래그와 함께 작동하지 않습니다.

-race 플래그를 사용하면서 코드 커버리지를 생성하려면 -covermode count에서 -covermode atomic로 전환해야 합니다. 이는 -covermode count보다 느립니다. 자세한 내용은 이 블로그 글을 참조하세요.

테스트 실행:
  stage: test
  image: golang:1.17
  script:
    - go install
    - go test ./... -coverprofile=coverage.txt -covermode count
    - go get github.com/boumenot/gocover-cobertura
    - go run github.com/boumenot/gocover-cobertura < coverage.txt > coverage.xml
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage.xml

Ruby 예시

다음은 Ruby에 대한 .gitlab-ci.yml 예시입니다.

  • 테스트를 실행하기 위해 rspec를 사용합니다.
  • 코드 커버리지 프로필을 기록하고 Cobertura XML 형식으로 보고서를 생성하기 위해 simplecovsimplecov-cobertura를 사용합니다.

이 예시는 다음을 전제로 합니다.

  • 의존성 관리를 위해 bundler가 사용되고 있다. rspec, simplecov, simplecov-cobertura 젬이 Gemfile에 추가되었습니다.
  • CoberturaFormatterSimpleCov.formatters 구성에 spec_helper.rb 파일에서 추가되었습니다.
테스트 실행:
  stage: test
  image: ruby:3.1
  script:
    - bundle install
    - bundle exec rspec
  artifacts:
    reports:
      coverage_report:
        coverage_format: cobertura
        path: coverage/coverage.xml

Troubleshooting

테스트 커버리지 시각화 표시되지 않음

테스트 커버리지 시각화가 차이 보기에서 표시되지 않는 경우, 커버리지 보고서를 확인하여 다음을 검증할 수 있습니다.

  • 차이 보기에서 보고 있는 파일이 커버리지 보고서에 언급되어 있는지 확인합니다.
  • 보고서의 sourcefilename 노드가 예상 구조를 따라야 하며, 이는 귀하의 리포지터리의 파일과 일치해야 합니다.
  • 파이프라인이 완료되었는지 확인합니다. 파이프라인이 매뉴얼 작업으로 차단된 경우, 파이프라인은 완료된 것으로 간주되지 않습니다.
  • 커버리지 보고서 파일이 제한 사항을 초과하지 않습니다.

보고서 artifacts는 기본적으로 다운로드할 수 없습니다. 작업 상세 페이지에서 보고서를 다운로드할 수 있도록 하려면 보고서를 artifact paths에 추가하세요.

artifacts:
  paths:
    - coverage/cobertura-coverage.xml
  reports:
    coverage_report:
      coverage_format: cobertura
      path: coverage/cobertura-coverage.xml