Now in REALITY Tech #80 self-hosted runnerでUnityの自動テスト環境を構築してみる
REALITYのUnityエンジニアのホンダです。
今週の「Now in REALITY Tech」では、自動テスト環境をGitHubのself-hosted runnerを使って構築したので簡単に紹介したいと思います。
self-hosted runnerとは
詳しくはGitHubのドキュメントを読んでもらえればと思いますが、簡単に説明すると以下の通りです。
GitHub Actions からジョブを実行するためにデプロイおよび管理するシステム
GitHubが管理しているGitHub ホステッド ランナーよりも細かく制御できる
物理または仮想にでき、コンテナー内、オンプレミス、またはクラウドに配置できる
GitHub Actionsを実行する環境を自前で設定できるという感じですね。
今回はGithub Actionsをトリガーとして、細かく制御できる自前の物理マシンでUnityテストを実行したかったのでself-hosted runnerを使って実装することにしました。
self-hosted runnerを使用する上での課題
実は使用する上で、課題が大きく2つあります。
1つのself-hosted runnerが同じ時間に処理できるjobは1つだけ
動作する環境は複数のjob間で使い回される
1の課題については登録されている環境が1つだけの場合、複数のjobが実行された場合、キューに積まれ処理されるまでに時間がかかってしまう可能性があります。
2の課題については、self-hosted runnerはjobのディレクトリの状態を維持されるのでactions/checkoutで git clean -ffdx が実行されることで毎回クリーンな環境でjobを実行できるようにしています。同一の環境で複数のjobが実行されるのであればそれはありがたいことですが、Unityを使用する上では結構問題で、プロジェクトの規模によってはキャッシュが効かず余分な時間がかかってしまいます。
1の課題については、今回は自動テストを実行することだけが目的で、他のjobは現状実行する予定はないので、オフィスにあるビルド用の物理マシンを3台使用してそれぞれをself-hosted runnerとしました。
本格的にオートスケーリングを考えるのであればこちらが参考になるかもしれません。
2の課題をどう回避したかは後ほど説明します。
self-hosted runner環境を構築する
構築方法は簡単です。リポジトリ設定(①)からメニューのActions > Runners(②)を開き、New self-hosted runner(③)をおして、表示されるコマンドを対象にしたいマシンで実行するだけです。
マシン名や、ラベルもつけることができます。後述するGitHub Actionsで識別するのに使用します。
GitHub Actionsで自動テストを実行する
環境ができたら、ワークフローを記述していきましょう。Actions自体の構造や実装方法については割愛します。
ジョブのランナーを選択する
self-hosted runnerを使用する上でポイントとなるのが、runs-on構文でself-hostedラベルを指定することです。ここで環境構築時に指定したラベルも渡すことで実行する環境を指定することもできます。
runs-on: [self-hosted, linux, hoge]
self-hostedラベルはすべてのセルフホステッド ランナーに指定されているものです。self-hosted ラベルは必須ではありませんが、公式ドキュメントでは下記理由からつけることを推奨しています。
ワークスペースをjobごとに分離する
これは使用する上での課題の2への対応で、jobごとに実行するワークスペースを分離して、キャッシュが効くようにしています。
具体的にはDeNAさんが公開しているこちらの仕組みを使わせていただきました。仕組みの詳細についてはこちらの記事で丁寧に説明されているのでぜひ読んでみてください。
使い方はジョブのステップでuses: DeNA/setup-job-workspace-action@v2を追加するだけです。ポイントとしてディレクトリを作成してシンボリックリンクなどを追加する仕組みのため、actions/checkoutよりも前に実行される必要があります。
実際にコードを書いてみる
コードとして書くとこんな感じになりました。GitHubで"automated test"というラベルを追加して、プルリクエストにラベルが付けられたタイミングで自動でテストが実行されるようにしてみました。
name: PullRequestTest
on:
# ここはActionsの実行タイミングを各自設定してください
pull_request:
types: [ labeled ]
jobs:
Test:
# matrixを使用してAndroidとiOSそれぞれのOSを対象としたテストを実行する
strategy:
fail-fast: false
matrix:
os: [Android, iOS]
# self-hostedを指定することで実行対象のマシンをself-hosted runnerとして構築したマシンを対象とする
runs-on: self-hosted
# automated testというラベルが付けられたタイミングで実行するようにしています
# ここも各自設定してください
if: |
((github.event.action == 'labeled') && (github.event.label.name == 'automated test'))
steps:
# 作業ディレクトリを分離する
# checkoutの前に呼ぶ必要あり
# https://swet.dena.com/entry/2023/02/28/100000
- uses: DeNA/setup-job-workspace-action@v2
with:
workspace-name: |
Test_${{matrix.os}}
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
lfs: true
# キャッシュを効かせるためにclean: false を指定する
# git clean -ffdx が実行されないようにする
clean: false
- name: Unity Play Mode Test
run: make test BUILD_TARGET=${{matrix.os}} TEST_PLATFORM=playmode
- name: Unity Edit Mode Test
run: make test BUILD_TARGET=${{matrix.os}} TEST_PLATFORM=editmode
# テスト結果をアップロード
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v3
with:
name: ${{matrix.os}}_test_results
path: TestResults/
matrixを使用することでiOSとAndroidの各OSで実行されるようにしています。またactions/checkoutの部分ではjobごとにワークスペースが分けられるようになったことでクリーンな環境にする必要がなくなったため、キャッシュを活用するためにclean: falseを指定しています。
name: Unity Play Mode Testの部分が実際にテストを実行している部分ですが、MakeFileで定義されていて実装的にはバッチモードでテストを実行しているだけです。
test:
$(Unityのパス) \
-batchmode \
-nographics \
-projectPath $(プロジェクトのパス) \
-buildTarget $(BUILD_TARGET) \
-debugCodeOptimization \
-burst-disable-compilation \
-testPlatform $(TEST_PLATFORM) \
-runTests \
-testResults $(テスト結果の書き出し先) \
これで自動テスト環境を構築することができました!
まとめ
最近チームでもGitHub Actionsを使った自動化が進んできており、self-hosted runnerも気になっている機能だったので試すいい機会でした。今の所は自動テストでしか使っていませんが、他にもself-hosted runner上で実行したいjobが増えていった場合はオートスケーリングも視野に入れる必要がありそうです。テストが増えていった際に実行時間が伸びるなど色々課題はありそうですが引き続き改善していきたいと思います。