Edited at

GitLab-CI/CDで複数環境でのテストを簡素に書く

GitLab.comにはGitLab-CI/CDが一緒になっているので、ライブラリ・パッケージ開発にも重宝してるのですが、悩ましいのが「バージョンのサポート」です。

それでも、変に新しすぎる機能を突っ込んでやや古いぐらいのバージョンに対応できるのは避けたいので、なるべく多くのバージョンでのテストを行うのが望ましいですね。

Travis-CIだと、テスト時の言語バージョンを割と簡単に複数指定できるのですが、GitLab-CI/CDだとどれだけ簡素に書けるかを少し調べてました。


TL;DR


  • YAMLのアンカーとエイリアスが普通に使えるので、使いましょう。

  • 具体例は、 「テストのみの処理」を共通化する(before_scriptは使わない)


以降の例


  • ちょっと個人的興味を兼ねて手を出している、「PHPのEOLでないバージョン全てで動くパッケージを作る」でサンプルコードを書いてます


    • 基本的にはどんな言語でもいけるはず



  • テストコードとかも全く出す予定もなく、基本的にはYAMLが続きます


(ここからが本文)


まずは最新バージョンのテストジョブを定義する


.gitlab-ci.yml

phpunit:

image: php:7.2-cli-alpine
script:
- apk add git
- curl https://getcomposer.org/download/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php

普通ですね。

GitLab-CI/CDは他のCIサービスなどと同じような感じで、.gitlab-ci.ymlにジョブを定義すると、git pushをトリガーにジョブが走ります。 1


複数バージョンに雑に対応させる

一番最初のケースではPHP 7.2での動作を前提としていますが、PHP7.1での動作もちゃんと保証させたいとします。

とりあえず、雑にジョブを増やしてみましょう。


.gitlab-ci.yml

phpunit-7.2:

image: php:7.2-cli-alpine
script:
- apk add git
- curl https://getcomposer.org/download/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-7.1:
image: php:7.1-cli-alpine
script:
- apk add git
- curl https://getcomposer.org/download/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/


コピペするとすごく手早く増やせます。

ジョブの名前をphpunit-(PHPのバージョン)にしておくことで、分かりやすいようにしておきましょう。

これぐらいだと、特にそんなに面倒な印象はありませんね。

さて、このままPHP 7.0, 5.6まで範囲を広げると、こうなります。


.gitlab-ci.yml

phpunit-7.2:

image: php:7.2-cli-alpine
script:
- apk add git
- curl https://getcomposer.org/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-7.1:
image: php:7.1-cli-alpine
script:
- apk add git
- curl https://getcomposer.org/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-7.0:
image: php:7.0-cli-alpine
script:
- apk add git
- curl https://getcomposer.org/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-5.6:
image: php:5.6-cli-alpine
script:
- apk add git
- curl https://getcomposer.org/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/


うーん、かなり冗長ですね。

これ以上なにもしないなら問題はそこまで大きくはないです。

ただ、特殊なPHP拡張も必要なケースがあった場合は、毎回4箇所編集しないと駄目なのでよくありません。2


ジョブで共通する処理はまとめる


.gitlab-ci.yml

before_script:

- apk add git
- curl https://getcomposer.org/composer.phar > composer.phar
- php composer.phar install

phpunit-7.2:
image: php:7.2-cli-alpine
script:
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-7.1:
image: php:7.1-cli-alpine
script:
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-7.0:
image: php:7.0-cli-alpine
script:
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-5.6:
image: php:5.6-cli-alpine
script:
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/


GitLab-CI/CDでは、全ジョブの実行時に各ジョブのscriptより前に実行したい共通処理をbefore_script上に書くことができます。

これによって、「テスト時に同じ処理を共通化して編集コストを減らす」ことが実現できます 3


テスト以外があったら?

GitLab-CI/CDはCIだけでなくCDも視野に入れています。

だからというわけではないのですが、


  • パッケージのリリースもGitLab-CI/CDを止めたい

  • ユニットテストの前に文法チェック(php -lphanなどなど)をやってNGならパイプラインを止めたい

などなど、単純なテスト以外のジョブも混ざってきます。

となってくると、before_scriptにいろいろ詰め込みすぎると「リリースには全く必要がないのに、composer installをしないといけない」などという事態になって、よろしくありません。


「テストのみの処理」を共通化する(before_scriptは使わない)


.gitlab-ci.yml

phpunit-7.2: &phpunit-latest

image: php:7.2-cli-alpine
script:
- apk add git
- curl https://getcomposer.org/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-7.1:
<<: *phpunit-latest
image: php:7.1-cli-alpine

phpunit-7.0:
<<: *phpunit-latest
image: php:7.0-cli-alpine

phpunit-5.6:
<<: *phpunit-latest
image: php:5.6-cli-alpine


YAMLには「アンカー」と「エイリアス」という仕様があり、「&でアンカーを定義」「*でエイリアスとしてアンカーのデータ構造をまるっと参照」が可能です。

これによって、<<: *(名前)によるアンカー先のデータをそのまま使いまわせるため、scriptを記述せずにジョブを増やす」ことが可能になります。

こんなことも可能です。(PHPのバージョンは同じだけど、 composerのバージョンを変える例)


.gitlab-ci.yml

phpunit-7.2: &phpunit-latest

image: php:7.2-cli-alpine
variables:
COMPOSER_VERSION: 1.7.2
script:
- apk add git
- curl https://getcomposer.org/download/${COMPOSER_VERSION}/composer.phar > composer.phar
- php composer.phar install
- vendor/bin/phpunit --bootstrap vendor/autoload.php tests/

phpunit-7.2-composer-1.6.5:
<<: *phpunit-latest
variables:
COMPOSER_VERSION: 1.6.5


これによって、「Laravelの複数バージョンにも対応している何か」も比較的テストしやすくなります。4


まとめ

ライブラリ開発のテストをCIなどでやると、記述内容の重複が起こってしまうケースがあります。

特に「Webアプリケーションフレームワークの拡張」を目的としたライブラリなどを開発していて、

可能な限り守備範囲を広げようとすると、言語バージョンxFWバージョンの掛け算でパターン数が爆発します。

YAMLの機能をきちんと使うことで、DRYでいけそうなシーンで適切にDRYな設定が書けるので、よりスリムなYAMLを目指したいと思います。


余談

phpunit-multiple-versions-in-gitlab-ci.png

前段で作ってるパッケージについて、ローカルで最低限のテストをパスしたプロジェクトを作って、↑の.gitlab-ci.ymlでCIを流した結果がこちら。

(テストコードの側で)PHP7.2から使えるメソッド使ってたりしてて、いきなり複数バージョン対応の大変さを思い知らされました。 5


外部リンク/参考情報など





  1. 最近は別のファイルを指定できるらしいですが、今回は触れません 



  2. 超余談ですが、この記事を書いてるタイミングで、テストジョブのスクリプトの間違いに気づいて、今まさに全部編集して良くないことになりました。 



  3. ただし、各ジョブごとにscriptを空にすることはできないので、「全く同じコマンド」しかなくても、何かしらscriptを書く必要があります。 



  4. さらに、ジョブ内でbefore_scriptの定義もできるため、「バージョン間で事前準備がほんの少しだけ違う」にも対応可能なはず 



  5. その他、PHPUnitのバージョン対応問題とか、関数定義時の型宣言の対応量とか、敵が多いです