GitLab CI - 在 Merge Request(MR) 中檢視 PHP 測試代碼覆蓋 (Code Coverage) 情況

2022.06.30 Update

於GitLab CI/CD 手冊上發現 artifacts:reports:cobertura 在 GitLab 15.0 中移除,替換為 artifacts:reports:coverage_report,因此更新底下內容。

2021.06.14 Update

在上一篇 在 Pipeline 檢視 PHPUnit 單元測試報告,透過 artifacts:reports:junit 的機制,使每次 Pipeline 執行後,就能馬上看到單元測試報告,有了測試報告之後,可能就會開始思考,那 Code Coverage 測試代碼覆蓋的狀況,是不是也可以在 Merge Request 的過程中就看到呢?

答案是可以的,而且在 GitLab CI/CD 的免費版本中就有這個功能。在 GitLab 12.9 版後,只需要透過 artifacts:reports:coverage_report 並作相關設定,就可以搜集 Cobertura XML 格式的報表就可以完成。這部分與 artifacts:reports:junit 的原理是一樣的,只需要讓 Pipeline 的工作可以產出所需要的格式的檔案,透過 artifacts:reports:coverage_report 將檔案搜集起來,就可以在 Merge Request 的畫面中看到視覺化的測試代碼覆蓋情況。

Test Coverage Visualization

那麼,在 PHP 該怎麼做出符合Cobertura XML格式的測試程式碼覆蓋狀況檔案呢?

如上圖(取自官方Test coverage visualization文件圖片),在圖片中,呈現綠色線條的部分,代表測試有執行到的部分,而橘色的部分則代表沒有測試程式覆蓋到。

在 GitLab 官方文件上提到了幾個語言及其支援的工具或外掛,如:

在 PHP 搭配 PHPUnit 該如何產出 Cobertura XML 呢?

一、讓 PHPUnit 輸出 Coverage Cobertura XML 格式的報表

在 PHPUnit 9.5 版的手冊中,關於 coverage 的章節中,可以看到,目前手冊上顯示如果要輸出報表,總共支援了底下的六種格式:

  • clover
  • crap4j
  • html
  • php
  • text
  • xml

其中,並沒有提到 GitLab CI/CD 要使用的 Cobertura XML 格式,因此甚至有人寫了轉換工具soyhuce/phpunit-to-cobertura,但仔細探究 PHPUnit 的原始碼,搜尋「cobertura」後發現了一道曙光phpunit/Builder.php at sebastianbergmann/phpunit),在 phpunit 的指令中,支援了 coverage-cobertura 關鍵字。

於是實際驗證,在已經開啟 xdebug 的 PHP 環境中,執行:

1
php vendor/bin/phpunit --coverage-cobertura=cobertura.xml

真的順利產出 Cobertura XML 格式的報表,樣式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">
<coverage line-rate="0.5" branch-rate="0" lines-covered="1" lines-valid="2" branches-covered="0" branches-valid="0" complexity="2" version="0.4" timestamp="1623610530">
<sources>
<source>/Users/mouson/GitFolder/GitLabCI-Playground/GitLab-CodeCoverage-Report-Demo/src</source>
</sources>
<packages>
<package name="" line-rate="0.5" branch-rate="0" complexity="2">
<classes>
<class name="Mouson\Template\Sample" filename="Sample.php" line-rate="0.5" branch-rate="0" complexity="2">
<methods>
<method name="isTrue" signature="" line-rate="0" branch-rate="0" complexity="1">
<lines>
<line number="9" hits="0"/>
</lines>
</method>
<method name="not" signature="bool $bool" line-rate="1" branch-rate="0" complexity="1">
<lines>
<line number="14" hits="1"/>
</lines>
</method>
</methods>
<lines>
<line number="9" hits="0"/>
<line number="14" hits="1"/>
</lines>
</class>
</classes>
</package>
</packages>
</coverage>

這代表至少在 PHPUnit 9.x 的版本中,可以支援 --coverage-cobertura 來產出 Cobertura XML 格式報表。

二、如何在 phpunit.xml 中宣告要產出 Cobertura XML 格式的報表呢?

同樣的,在 PHPUnit 的官方手冊中,是沒辦法找到在 PHPUnit 設定檔中應該如何產出 Cobertura XML 格式報表的,但如同在第一章中驗證的方法,在 coverage 的 report 做了 cobertura 的宣告:

1
2
3
4
5
6
7
8
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
<report>
<cobertura outputFile="build/cobertura.xml"/>
</report>
</coverage>

同樣產出了 Cobertura XML 格式報表,因此這在 phpunit.xml 或 phpunit.xml.dist 是可行的。

三、如何在 GitLab CI/CD 中整合,讓PHPUnit 產出 Cobertura XML 報表格式

在這次的範例,直接取用中上一篇在 Pipeline 檢視 PHPUnit 單元測試報告相同的原始碼,但由於 PHPUnit 要產出 Code Coverage 相關的資訊時,必須搭配 xdebug 使用,因此這邊改採 jorge07/alpine-php:8.0-dev docker image 作為執行環境,同樣以 PHP 8.0 作為 PHP 的版本。

1. GitLab CI 執行環境

2. 在 GitLab 共用的模板

1
2
3
4
5
6
7
.test_template:
stage: test
image: "jorge07/alpine-php:8.0-dev"
before_script:
- export XDEBUG_MODE=coverage
- export COMPOSER_ALLOW_XDEBUG=1
- composer install --prefer-dist > /dev/null

在底下的範例中,使用的模板都使用以上的共用安裝過程及共用的 docker image jorge07/alpine-php:8.0-dev。值得一提的地方是,在 PHP 8.0 搭配 XDebug 3.0 後,在取得 Coverage 時,必須讓 XDebug 知道 XDEBUG_MODE 為 coverage。

四、在 GitLab CI 中執行 PHPUnit 的 Coverage 檢查,並產出 Cobertura XML 格式報表

1. 在 PHPUnit Command Line 執行指令中設定 Cobertura XML Report 格式

如前面所提到的,在 Command Line 中要得到 Cobertura XML 格式的報表,必須在 PHPUnit 執行的指令中加上 --coverage-cobertura filePath 參數,在 .gitlab-ci.yml 中,根據 上一篇 的範例原始碼,script 的部分必須再增加 --coverage-cobertura,增加後的 .gitlab-ci.yml 如下:

1
2
3
4
5
6
7
8
9
10
test:phpunit-with-command-line:
extends: .test_template
script:
- php vendor/bin/phpunit --coverage-cobertura build/cobertura.xml --log-junit build/junit.xml
artifacts:
reports:
junit: build/junit.xml
coverage_report:
coverage_format: cobertura
path: build/cobertura.xml

如上,再增加後,可以在對應的 Merge Request 馬上看到成果:

coverage visualization

如上圖,在範例的 PHP 程式碼中,刻意的不測試 isFalse 這個方法,因此方法中的原始碼呈現 No test coverage。

2. 透過 phpunit.xml.dist 或 phpunit.xml 設定 Cobertura XML 格式 Coverage 報表

同樣的在第二段中,有提到如果要將 Cobertura XML 設定在 phpunit.xml 中,則 Coverage 的部分必須增加 report 並且增加 cobertura 的段落,範例如下:

1
2
3
4
5
6
7
8
<coverage processUncoveredFiles="true">
<include>
<directory suffix=".php">./src</directory>
</include>
<report>
<cobertura outputFile="build/report.cobertura.xml"/>
</report>
</coverage>

這邊的 phpunit.xml 將 Cobertura 格式的檔案輸出到 build/report.cobertura.xml 這個檔案路徑。因此對應的 GitLab CI Pipeline 描述,也需要針對報表的路徑做調整,如下:

1
2
3
4
5
6
7
8
9
10
test:phpunit-with-phpunit-file:
extends: .test_template
script:
- php vendor/bin/phpunit
artifacts:
reports:
junit: build/report.junit.xml
coverage_report:
coverage_format: cobertura
path: build/report.cobertura.xml

3. 在 ParaTest 中產出 Cobertura XML 格式 Coverage 報表

在 PHP 語言中,為了讓 PHPUnit 可以執行的更快,開始有一些加強的方案,如很多人常用的平行化處理方案,透過多執行序來同時執行 PHP 的單元測試以加快完成速度,其中 ParaTest paratestphp/paratest: Parallel testing for PHPUnit 就是這中間很有名的一個工具。如上一篇提到 ParaTest 大部分的指令都可以與 PHPUnit 支援的相容,在經過測試後,發現 Coverage Report 產出 Cobertura XML 格式報表的部分,是相容的,因此幾乎不太需要更動。如下是在 ParaTest 的指令模式中產出 Cobertura XML 格式報表:

1
2
3
4
5
6
7
8
9
10
11
test:paratest-with-phpunit-file:
extends: .test_template
script:
- mkdir -p build
- vendor/bin/paratest --colors --processes 2 --runner=WrapperRunner
artifacts:
reports:
junit: build/report.junit.xml
coverage_report:
coverage_format: cobertura
path: build/report.cobertura.xml

上面內容與上一篇一樣 mkdir -p build 是為了避免無法寫入檔案造成無法產出報表。其他的如 PHPUnit 產出 Cobertura XML 格式報表,僅加入了 artifacts:reports:cobertura 來取得 Cobertura XML 格式報表對應的檔案。

4. 接下來就是使用 Command Line 產出報表的範例:

1
2
3
4
5
6
7
8
9
10
11
12
test:paratest-with-command-line:
extends: .test_template
script:
- mkdir -p build
- vendor/bin/paratest --colors --processes 2 --runner=WrapperRunner --coverage-cobertura build/cobertura.xml --log-junit build/junit.xml
artifacts:
reports:
junit: build/junit.xml
coverage_report:
coverage_format: cobertura
path: build/cobertura.xml

同上一個範例中,也同樣有 mkdir -p build 這段產出 build 資料夾的部分。其它的如 PHPUnit 在 Command Line 一樣,要產出 Cobertura XML 格式的報表必須要透過 --coverage-cobertura build/cobertura.xml 參數。

五、結論

在 GitLab CI 中使 PHP 的測試工作 PHPUnit 執行後產出 JUnit XML 格式的報表,主要倚靠 PHPUnit 自身的功能搭配 GitLab CI artifacts:reports:junit 機制來完成,而,如果需要更有效的看到視覺化的 Code Coverage 則可以透過 artifacts:reports:cobertura,在 PHP 的環境中,透過小祕技產出 Cobertura XML 其實只是增加一兩段指令,不會太難,最難的部分反而是從 PHPUnit 原始法中找到 coverage-cobertura 這個宣告。

希望這篇文章,希望對大家有幫助。

六、參考連結

作者: 墨嗓 (陳佑竹)
連結: https://mouson.im/Notes/GitLab/gitlab-show-php-code-coverage-in-merge-request/

当前网速较慢或者你使用的浏览器不支持博客特定功能,请尝试刷新或换用Chrome、Firefox等现代浏览器