PHPUnit のコードカバー率がマージリクエスト(MR)の前後でどのように変化したかの差分を MR の画面に表示するようにしてみたメモ。
.gitlab-ci.yml
は次のような内容になります。phpunit でテストを実行するジョブと phpcov でコードカバレッジを計測するジョブが分かれていますがこれは本題とは関係ありません。実際のプロジェクトでそうしていることが多いため、この例でも同じようにしているだけです。
image: ngyuki/php-dev
stages:
- test
- coverage
test:
stage: test
only:
- master
- merge_requests
script:
- composer install --prefer-dist --no-progress --no-suggest --ansi
- mkdir -p phpcov/
- phpdbg -qrr vendor/bin/phpunit --coverage-php=phpcov/test.cov
cache:
paths:
- vendor/
artifacts:
paths:
- phpcov/
expire_in: 1 days
.coverage:
stage: coverage
needs:
- test
script: &coverage_script
- composer install --prefer-dist --no-progress --no-suggest --ansi
- vendor/bin/phpcov merge phpcov/ --text=coverage.txt
- sed -r '/[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}/d' -i coverage.txt
cache:
paths:
- coverage.txt
- vendor/
coverage:
extends: .coverage
only:
- master
cache:
key: coverage--$CI_COMMIT_REF_NAME
policy: push
coverage-mr:
extends: .coverage
only:
- merge_requests
cache:
key: coverage--$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
policy: pull
script:
- test -e coverage.txt && mv -f coverage.txt coverage.orig.txt || true
- *coverage_script
- test -e coverage.orig.txt && diff -u -w coverage.orig.txt coverage.txt
コードカバレッジを計測するジョブで phpcov merge
の --text
でテキストのコードカバレッジをファイルに書き出します。そのファイルを diff
で比較してその結果をジョブのログに出力します。
ポイントは、master で実行されるジョブのキャッシュと MR で実行されるジョブのキャッシュを同名にすることで、master のジョブで作成された coverage.txt を MR のジョブで参照する、ところです。
master のジョブではキャッシュ名を coverage--$CI_COMMIT_REF_NAME
としています。$CI_COMMIT_REF_NAME
はブランチ名になるのでキャッシュ名は coverage--master
となります。
MR のジュブではキャッシュ名を coverage--$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
としています。$CI_MERGE_REQUEST_TARGET_BRANCH_NAME
はマージリクエストのマージ先ブランチなので、master へのマージリクエストならキャッシュ名は coverage--master
となり、master のジョブのキャッシュ名と同名になります。
さらに master のジョブでは policy: push
でキャッシュを更新するのみ、MR のジョブでは policy: pull
を取得のみとします。
これで MR のジョブの実行時に master のジョブによって作成されたキャッシュを取得できます。キャッシュに phpcov --text
の出力ファイルを入れてやれば、MR のジョブで次のような内容をログに出力することができます。
--- coverage.orig.txt
+++ coverage.txt
@@ -3,11 +3,11 @@
Code Coverage Report:
Summary:
- Classes: 0.00% (0/2)
- Methods: 50.00% (3/6)
- Lines: 50.00% (3/6)
+ Classes: 50.00% (1/2)
+ Methods: 83.33% (5/6)
+ Lines: 83.33% (5/6)
App\Sample1
- Methods: 66.67% ( 2/ 3) Lines: 66.67% ( 2/ 3)
+ Methods: 100.00% ( 3/ 3) Lines: 100.00% ( 3/ 3)
App\Sample2
- Methods: 33.33% ( 1/ 3) Lines: 33.33% ( 1/ 3)
+ Methods: 66.67% ( 2/ 3) Lines: 66.67% ( 2/ 3)
MR のコメントにカバー率の差分を追記する
↑だけだとジョブのログに出力されるだけなので、MR の画面からパッと確認できなくて不便です。ので、カバー率の差分を MR のコメントとして追記するようにします。
まず、次のようなスクリプトを ci/coverage-reporter.sh
のようなファイル名で用意します。
: ${COVERAGE_REPORTER_TOKEN:?}
: ${CI_API_V4_URL:?}
: ${CI_PROJECT_ID:?}
: ${CI_MERGE_REQUEST_IID:?}
user_id=$(
curl -s -H "PRIVATE-TOKEN: $COVERAGE_REPORTER_TOKEN" "$CI_API_V4_URL/user" \
| jq .id -r
)
note_id=$(
curl -s -H "PRIVATE-TOKEN: $COVERAGE_REPORTER_TOKEN" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
| jq '.[] | select(.author.id|tostring == $user_id).id' --arg user_id "$user_id" -r
)
diff=$(cat -)
if [ -z "$diff" ]; then
diff="no difference in code coverage"
fi
body=$(printf '```diff\n%s\n```\n' "$diff")
data=$(jq -n '{body:$body}' --arg body "$body")
if [ -z "$note_id" ]; then
curl -s -H "PRIVATE-TOKEN: $COVERAGE_REPORTER_TOKEN" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes" \
-X POST -H content-type:application/json --data-raw "$data" \
> /dev/null
else
curl -s -H "PRIVATE-TOKEN: $COVERAGE_REPORTER_TOKEN" \
"$CI_API_V4_URL/projects/$CI_PROJECT_ID/merge_requests/$CI_MERGE_REQUEST_IID/notes/$note_id" \
-X PUT -H content-type:application/json --data-raw "$data" \
> /dev/null
fi
標準入力から入ってきたテキストを、まだ MR にコメントしていなければ新規コメントを、既に MR にコメントしているならそのコメントの修正を、行うスクリプトです。
MR へのコメントのためにアクセストークンが必要なので、プロジェクトレベルのアクセストークンなどを作成の上、Gitlab CI の Variable で COVERAGE_REPORTER_TOKEN
という名前で設定しておく必要があります。
そして、coverage-mr
ジョブの script
の最後を次のように修正します。
coverage-mr:
script:
- if test -e coverage.txt; then mv -f coverage.txt coverage.orig.txt; fi
- *coverage_script
- if test -e coverage.orig.txt; then (diff -u -w coverage.orig.txt coverage.txt || true) | ci/coverage-reporter.sh; fi
これで次のようなコメントが MR に追記されるようになります。
さいごに
Github で coveralls や codecov などの SaaS を組み合わせればテストのカバレッジの可視化やカバー率の差分が記録できるらしいです。
例えば PHPUnit だと次のように codecov でカバー率の差分が PR に追記されています。
Gitlab 単体でも PHPUnit のコードカバレッジの HTML レポートをアーティファクトに保存したり Gitlab Pages で公開したりすればカバレッジの可視化には十分なのですが・・・コードカバー率の差分も表示できると有用かもー、と思ったので簡易的にやってみました。
欠点としては・・・MR のコメントに追記すると、カバー率が減っていても増えていても変わらなくても、最初の1回だけは常にメールで通知されてきます。レビュー時の参考情報ぐらいの位置づけにしたかったので通知は不要なのですが。
なお、今回はキャッシュを使って master のジョブの実行結果を MR のジョブで取得するようにしましたが、下記の API を使えばアーティファクト経由でもできそうです。