トラックの継続的インテグレーション(CI)を設定することはとても重要です。ミスを見つける助けになるからです。
Exercismのリポジトリ(トラックのリポジトリも含みます)では、CIの実行にGitHub Actionsを使っています。 GitHub Actionsは_ワークフロー_を基本としていて、特定のイベント(たとえばコミットのプッシュ)が発生するたびに自動的に実行するスクリプトを定義します。 GitHub Actionsのワークフローについて詳しくは、ワークフローのドキュメントを参照してください。
トラックにはあらかじめいくつものワークフローがインストールされています。そのほとんどは変更すべきでは_ありません_(これらは_共有ワークフロー_と呼ばれます)。
ただし、変更すべきワークフローが1つだけあります。それがtest.ymlワークフローです。
test.ymlワークフローの目的は、トラックの演習が正しい状態にあることを検証することです。
このワークフローは、mainブランチまたはプルリクエストのブランチにプッシュが行われたときに自動的に実行されるよう設定されています(GitHub Actionsの用語では_トリガー_されます)。
ワークフロー自体が行うことは多くありません。次のことだけです。
すでに触れたとおり、演習はスクリプト、つまりbin/verify-exercises(bash)スクリプトで検証します。
このスクリプトは_ほぼ_完成していて、次のことを行います。
unskip_tests関数を呼び出す。この中でテストファイルのテストのスキップを解除できます(任意)run_tests関数を呼び出す。この中でテストを実行します(必須)run_testsとunskip_tests関数が、実装する必要のある唯一のものです。
トラックがテストのスキップをサポートしている場合、演習のexample/exemplarの解答を検証するときは、テストが1つもスキップされないようにする必要があります。 一般に、トラックがテストの「スキップ解除」をサポートする方法は2つあります。
test.skipをtestに変更します。SKIP_TESTS=falseを設定します。テストのスキップがファイルベースの場合(上記の1つ目の方法)、unskip_tests関数を編集してテストファイルを変更します(テストファイルをループする処理は既存のコードにすでにあります)。
unskip_test関数は演習ディレクトリのコピーに対して実行されるので、ファイルは自由に変更してかまいません。
Arturoトラックのbin/verify-exercises fileは、sedを使ってテストファイル内のテストのスキップを解除します。
unskip_tests() {
jq -r '.files.test[]' .meta/config.json | while read -r test_file; do
sed -i 's/test.skip/test/g' "${test_file}"
done
}
テストのスキップ解除に環境変数の設定が必要な場合は、それがrun_tests関数で設定されていることを確認してください。
run_tests関数は、演習のテストを実行する役割を担います。
この関数が呼ばれるときには、example/exemplarのファイルはすでに(スタブの)解答ファイルにコピーされているので、テストを実行するための正しいコマンドを呼び出すだけでかまいません。
すべてのテストがパスしたら、この関数は終了コードとしてゼロを返さなければなりません。そうでなければ、ゼロ以外の終了コードを返します。
run_tests関数は演習ディレクトリのコピーに対して実行されるので、ファイルは自由に変更してかまいません。
演習を検証するスクリプトの既定の方法は、その言語のツール(SDKやバイナリなど)を使うもので、ほとんどのトラックがこの方法を使っています。 テストの実行方法はトラックごとに異なりますが、通常はコマンド1つだけです。
Arturoトラックのbin/verify-exercises fileは、run_tests関数を変更して、テストファイルに対して単にarturoコマンドを呼び出します。
run_tests() {
arturo tester.art
}
2つ目の方法は、トラックのテストランナーを実行して演習を検証するものです。 もちろんこれは、トラックに動作するテストランナーがあることが前提です。
トラックにまだテストランナーがない場合は、次のどちらかを行います。
既定のbin/verify-exercisesスクリプトには、次の変更を加える必要があります。
dockerコマンドが使えることを確認するdocker runを使って、各演習でテストランナーのDockerイメージを実行するjqを使って、Dockerコンテナが返すresults.jsonファイルがすべてのテストのパスを示していることを検証するunskip_test関数と、その呼び出しを削除するこの方法の主な利点は、本番環境(ウェブサイト上)でテストがどのように実行されるかを最もよく模倣できることです。 この方法なら、CIではパスしたのに本番環境で失敗するという事態が起こりにくくなります。 欠点は、DockerイメージのプルとDockerのオーバーヘッドのため、通常は遅くなることです。
Unisonトラックのbin/verify-exercises fileは、dockerコマンドもインストールされていることを検証するチェックを追加します。
required_tool docker
続いて、トラックのテストランナーイメージをプルします。
docker pull exercism/unison-test-runner
次に、run_tests関数を変更して、docker runで現在の演習(作業ディレクトリにあります)に対してテストランナーを実行し、続けてjqコマンドで正しいステータスを確認します。
run_tests() {
local slug
slug="${1}"
docker run \
--rm \
--network none \
--mount type=bind,src="${PWD}",dst=/solution \
--mount type=bind,src="${PWD}",dst=/output \
--tmpfs /tmp:rw \
exercism/unison-test-runner "${slug}" "/solution" "/output"
jq -e '.status == "pass"' "${PWD}/results.json" >/dev/null 2>&1
}
最後に、run_testsコマンドの呼び出しを変更する必要があります。slugが必要になったからです。
run_tests "${slug}"
verify-exercisesスクリプトが完成したので、次はtest.ymlワークフローを仕上げます。
その方法は、verify-exercisesスクリプトの実装でどのオプションを選んだかによって変わります。
verify-exercisesスクリプトが言語のツールを直接使う場合、テストワークフローでは次をインストールする必要があります。
それが済めば、verify-exercisesは期待どおりに動作するはずです。これでCIの設定は完了です。
例については、Arturoトラックのtest.ymlワークフローを参照してください。
name: Test
on:
push:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:
jobs:
ci:
runs-on: ubuntu-22.04
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Install dependencies
run: |
sudo apt-get update
sudo apt-get install libgtk-3-dev libwebkit2gtk-4.0-dev libmpfr-dev
- name: Install Arturo
run: bin/install-arturo
env:
GH_TOKEN: ${{ github.token }}
- name: Verify all exercises
run: bin/verify-exercises
2つ目の方法は、トラックのテストランナーを実行して演習を検証するものです。 この方法には2つの条件があります。
verify-exercisesスクリプトがテストランナーのDockerイメージを使って演習のテストを実行することトラックにまだテストランナーがない場合は、次のどちらかを行います。
この方法にはいくつかの利点があります。
主な欠点は、DockerイメージのプルとDockerのオーバーヘッドのため、遅くなる可能性が高いことです。
テストランナーのDockerイメージをプルする方法はいくつかあります。
verify-exercisesファイル内でイメージをダウンロードする。
これはUnisonトラックが採用している方法です。では、どの方法を使えばよいのでしょうか? _少なくとも_1番の方法を実装して、verify-exercisesスクリプトを_単体で動く_ようにすることをおすすめします。
イメージが特に大きい場合は、3番の方法も実装するとよいかもしれません。ビルドしたDockerイメージがGitHub Actionsのキャッシュに保存され、以降の実行ではイメージをダウンロードする代わりにキャッシュから読み込めるので、パフォーマンスが良くなる可能性があります(必ず計測して確認してください)。
3つ目の代替案は、前の2つを組み合わせたハイブリッドです。
ここでもテストランナーのDockerイメージを使いますが、今回はverify-exercisesスクリプトを_そのDockerイメージの中で_実行します。
この方法を有効にするには、ワークフローのコンテナをテストランナーに設定します。
container:
image: exercism/vimscript-test-runner
そうすれば、依存関係とツールのインストール手順(それらはテストランナーのDockerイメージ内にインストール済みだからです)を省略でき、そのままbin/verify-exercisesスクリプトの実行に進めます。
vimscriptトラックのtest.ymlワークフローはこの方法を使っています。
name: Verify Exercises
on:
push:
branches: [main]
pull_request:
workflow_dispatch:
jobs:
ci:
runs-on: ubuntu-24.04
container:
image: exercism/vimscript-test-runner
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332
- name: Verify all exercises
run: bin/verify-exercises