DeNA Testing Blog

Make Testing Fun, Smart, and Delighting End-Users

Unityプロジェクト向けオートパイロットフレームワークの運用Tips

SWETグループの長谷川( @nowsprinting )です。

開発者自身の手によるUnityプロジェクトの品質向上アプローチのひとつに、ゲームプレイを自動化するオートパイロットによる検証があります。 このアプローチについて、DeNA内で開発・導入を進めているフレームワークAnjinを昨年紹介しました。

swet.dena.com

また、Anjin自体も先日オープンソース化しました。こちらのリポジトリからご利用いただけます。

github.com

本記事では、Anjinの、主にオートパイロットを安定運用するための機能を紹介します。

Automated QAが保存するスクリーンショットの退避

uGUIコンポーネントのシナリオテストを行なう UGUIPlaybackAgent の実装には、 Automated QAパッケージ のRecorded Playback機能を使用しています。 Playback機能は再生操作の前後にスクリーンショットを撮影して Application.persistentDataPath 下に書き出してくれるのですが、新しい操作シナリオの実行がはじまる契機で削除されてしまいます。

Anjinでは複数のシナリオを連続して実行しますので、スクリーンショットを残すのであればすべてのシナリオ分を残したくなります。 そこでAnjinでは、シナリオ実行が終わるたびにAutomated QAの出力先パスを求め*1、退避させる手段を取りました。

詳しくは、UGUIPlaybackAgent クラスの StoreScreenshots メソッドを参照してください。

Unityエディター終了時のクラッシュ対策

Anjinを活用している開発チームでは、Anjinを夜間に定期実行されるように設定しています。 GitHub ActionsワークフローやJenkinsから起動されたAnjinは一定時間動作したらUnityエディターを終了しますが、このとき終了コード0以外で終わってしまうことがあります*2

これを起動元がエラーと判断してアラートを上げてしまうことを回避するため、Anjinは終了時にJUnit Report形式*3のXMLファイルを出力しています。 これにより起動元は、Unityエディターの終了コードではなく、JUnit Reportファイル内の errors の数で合否を判断できます。

JUnit Reportファイル出力の実装は、JUnitReporter クラスを参照してください。

サーバ通信エラーなどによるタイトル戻し対策

「タイトル戻し」とは弊社内での通称ですが、通信エラーなどで進行不能となったときに「タイトル画面に戻ります」といったボタンを表示してタイトル画面からやり直させる振る舞いのことを指します。 もしエラー発生時に動作していたのがモンキーAgentであれば、Agentはそのままタイトル画面に戻るボタンをクリックし、そのままAnjinの実行は継続されます。

しかし、プレイバックAgentなど操作シナリオを再現するAgentでは、本来クリックしたかったボタンの代わりに別のボタンが出現したことでシナリオ継続不可能となってしまい、アラートが上がってしまいます。 エラーの内容が報告すべきものであればこの振る舞いでよいのですが、通信エラーなど無視したいもの(むしろ継続して動作してほしいもの)もあります。 *4

この問題は、次のビルトインAgentを組み合わせることで回避できます。

EmergencyExitAgent

このAgentは、EmergencyExit コンポーネントがアタッチされたボタンの出現を監視し、Scene内に出現したら即時クリックする機能を持っています。 つまり、エラー発生時に表示される「タイトル画面に戻ります」ボタンにこれをアタッチしておけば、プレイバックAgentがシナリオ継続不能と判断する前にタイトルSceneへと遷移できるのです。 Sceneが切り替わった時点でプレイバックAgentは終了してタイトル画面用のAgentに切り替わります。

EmergencyExit は、Anjinが動作している間は常駐して監視を続ける必要があります。それには、AutopilotSettingsにある Observer Agent として登録して使用します *5

OneTimeAgent

たとえばタイトルSceneの操作は、起動後1回目の表示時と2回目以降で異なるシナリオを要求されることがほとんどではないでしょうか。 初回起動では年齢確認やチュートリアルの操作が必要になるためです。

このAgentは1つの子Agentを設定でき、それをオートパイロット実行期間を通じて1回だけ実行します。2回目以降の実行はスキップされます。 これを利用して次のようにAgentを組合わせることで、1回目と2回目以降で異なるシナリオを実行できます。

タイトルScene向け SerialCompositeAgent
    ├── 子1: OneTimeAgent
    │   └── 子: 1回目のシナリオの PlaybackAgent
    └── 子2: 2回目以降シナリオの PlaybackAgent

まとめ

このように、できるだけ単機能・最小限のAgentだけをビルトインAgentとして提供し、Unityエディター上で組み合わせることにより(ノーコードで)ゲームタイトルに合わせたオートパイロット動作を実現できるようにしています。

とはいえ、あまりに複雑な組み合わせを強いるのは本末転倒です。 複雑な要件に対しては、ゲームタイトル固有のカスタムAgent*6をエンジニア(プログラマー)が作成することを検討してください。

We are hiring

SWETグループでは、このようなUnityプロジェクトに対する品質向上アプローチも行っています。 興味を持たれた方は、ぜひ採用情報を確認ください!

*1:パスにはタイムスタンプが含まれるため

*2:プロジェクトによっては高頻度で発生していました

*3:JUnit形式を選択したのは、これを扱えるライブラリがGitHub ActionsやJenkinsプラグインに存在するためです

*4:Anjinはログを監視してエラーを報告しますが、指定した文字列を含むエラーログを無視する設定ができます。前回のブログ記事を参照してください

*5:Observer Agent に設定されたAgentは、DontDestroyOnLoad ではなく、新しいSceneがロードされる度に破棄・生成される点に留意してください

*6:AbstractAgent を継承して実装できます