要点
- モバイル端末上のテストをクラウドシフトすることで、新旧様々な検証用端末を購入するコストが抑えられ、またOSのバージョン管理から解放される。
- ただしC#で書かれるUnityは、既存のネイティブ言語用テストフレームワークとの互換性がないため、クラウド事業者が提供するマネージドサービスをそのまま使えるとは限らない。
- その中でFirebase Test LabのGame Loop testならUnityを扱えることを確認した。ただしiOS上で実現した先行事例はなく、参考資料も見当たらないため、その導入手順をコード付きで解説する。
- 組み込み対象となるUnityのバージョンは
2018.1
以降を想定しているが、テスト実行に必須としない処理では2019.3
以降の機能にも言及している。
はじめに
はじめまして、AIシステム部MLエンジニアリンググループの 竹村( @stakemura ) です。普段は、AIの技術開発に携わっており、今回はその重要な動作対象プラットフォームの1つであるiOSと、ニーズの多いUnity上の自動テストに焦点を当てたいと思います。所属部署名からは想像しにくい内容で恐縮ですが、AI技術をモバイルアプリに正しく組み込むことも重要な業務の1つです。
実機テストのクラウドシフト
さて、皆さんはAWSやGCP、そしてAzureといったクラウド事業者が、いわゆるサーバーだけではなくモバイル端末のテスト用にマネージドサービスを提供していることをご存知でしょうか?本稿で述べる端末とは、もちろんエミュレータではなく実機そのものを指しており、ハードウェア依存のテストにも耐え得ることを前提とします。
モバイルアプリ開発は、次から次へと増え続ける端末、そして仕様が変わり続けるOSに対応し続けなければいけないという、PC上の開発にはない難しさがあります。また、新機種だけなく、どこまで古い端末をサポート対象とするかの検証も製品開発には必要です。このように、新旧様々な検証用端末を物理的に揃えるのはコストがかかりますし、OSのバージョン管理だって大変です。一方、会社として共有の開発用端末をオフィスに確保しているケースもあると思いますが、このリモートワーク時代、端末のためだけに出社するのも億劫という方も少なくないのではないでしょうか?
そこで、クラウドシフトです。前述のクラウド事業者は、モバイル端末の自動テスト用に、下記のサービス(以下、クラウドサービスと呼ぶ)を提供しています。
- AWS Device Farm - Amazon Web Services
- Firebase Test Lab - Google
- Visual Studio App Center - Microsoft
クラウドサービスの選定
ここから本題なのですが、テストしたいアプリの実装によっては「どのクラウドサービスを選定しても機能上問題ない」とは限りません。例えば、 AWS Device Farm の対応フレームワークを 該当ドキュメント から列挙してみましょう。
- Appium
- Calabash
- Instrumentation (JUnit, Espresso, Robotium …) for Android
- UI Automator for Android
- UI Automation for iOS
- XCTest
- XCTest UI
お気づきでしょうか?Unity
の名前がないことに。前述のどのクラウドサービスも、Espresso
やXCTest
といったネイティブ言語で利用することを前提としたテストフレームワークには対応しています。ところが、非ネイティブ言語であるC#を採用するUnityは、それらテストフレームワークとの互換性がないため、クラウドサービスとしてサポートされるとは限らないのです。もちろん、アプリの起動ならできるでしょうが、それだけだと別途テスト結果の可視化などを自前でサポートする必要が出てしまいます。一方で、AWS Device Farmの機能を有効活用するために、前述のテストフレームワーク、例えばiOSであればXCTestに適合できるように、
Unity as a Library
を使ってネイティブ言語でUnityモジュールの呼び出しを頑張るアプローチが考えられます。しかし、このアプローチではどうしてもiOSとAndroidで実装が大きく別れますし、C#のみでクロスプラットフォームアプリが書けるというUnityの手軽さがスポイルされてしまいます。
Unity製iOSアプリにおける選択肢
本稿で推奨するアプローチは、ずばりGoogleの Firebase Test Lab です。このクラウドサービスには、Game Loop test( iOS / Android )という、その名の通りゲームにおける利用を前提としたモードがあり、実はAndroid版Unityに関しては、Googleの Codelabs にて “Testing a Unity Project with Firebase Test Lab for Android” という名のドキュメントが公開されています。またドキュメントで参照しているunitypackageのコードは、 googlecodelabs/unity-firebase-test-lab-game-loop で公開されています。ですので、Android版Unityに関しては、Google公式のドキュメントやレポジトリが頼りになります。
一方、問題はiOSです。本稿執筆時点で、GoogleはiOS版Unityには言及しておらず、また特に他社の先行事例も見当たりませんでした。したがって、まだ誰も挑戦したことがない試みである可能性が高いのですが、Firebase Test LabはiOS版Unityも問題なく扱えることを当方で確認済みです。ただそのためにはC#ベースの追加実装が必要であり、皆さんが手軽に再現できるよう、これからその実装内容をコード付きで解説いたします。
なお補足になりますが、Unity同様にC#を実装言語とする Xamarin
のテストであれば、
App Center
を使うことを推奨します。こちらは、App Center公式ドキュメントの
Get Started with Xamarin
にて、AndroidとiOSの双方にSDKとして対応していることが示されています。
導入手順
Firebase projectの作成
Firebase Test Labを利用するためには、まずFirebase project
をご用意ください。Codelabsの
からの引用になりますが、作成手順は、以下の通りです。
- Firebase console を開く
Add project
をクリック- ダイアログで
Project name
を入力します。- 最後に、
Create Project
をクリックしてください。
gcloud CLIのセットアップ
続いて、
gcloud CLI
がインストールされていなければ、インストールすることを推奨します。Firebase Test LabはWeb UIで扱うことができますが、テストのように繰り返し行うものは、コマンドで自動実行できるようにすることが望ましいためです。macOSにおけるgcloud CLIのインストールコマンドは下記の通りとなります。なお、本稿はGoogle Cloud SDK 309.0.0
を元に執筆しております。
$ curl https://sdk.cloud.google.com | bash
そして下記コマンドを実行して、Firebase projectを作るときに用いたGoogle Accountにログインしてください。なおCI用に、認証鍵のJSONファイルを使って認証する方法もあります。その場合は、 gcloud auth activate-service-account を参照してください。
$ gcloud init
次に、Firebase projectを作成すると、Project ID
が自動的に生成されます。これは、
Firebase console
の各プロジェクトのpane上に表示されているもので、下記コマンドでテストに用いるプロジェクトを指定してください。
$ gcloud config set project ${GOOGLE_PROJECT_ID}
ここまで設定が終わったら、試しにFirebase test labがサポートするiOS端末やOSのバージョンを下記コマンドでチェックしてみましょう。途中割愛しておりますが、以下のような出力になるはずです。
$ gcloud firebase test ios models list
┌─────────────┬───────────────────────┬─────────────────────┬──────────────────────────────────┐
│ MODEL_ID │ NAME │ OS_VERSION_IDS │ TAGS │
├─────────────┼───────────────────────┼─────────────────────┼──────────────────────────────────┤
│ ipad5 │ iPad (5th generation) │ 11.2,12.0 │ deprecated=11.2 │
...
│ iphonexsmax │ iPhone XS Max │ 12.0,12.1,12.3 │ │
└─────────────┴───────────────────────┴─────────────────────┴──────────────────────────────────┘
WARNING: Some devices are deprecated. Learn more at https://firebase.google.com/docs/test-lab/available-testing-devices#deprecated
余談ですが、Androidは下記コマンドで140種類以上の端末がリストアップされることが確認できます。
$ gcloud firebase test android models list
Firebase Test LabのUnity組み込み
ここからは、Firebase公式ドキュメントの
Get started with Game Loop tests for iOS
を参照しつつ解説いたします。なお、Unityのバージョンは2018.1
以降を想定しています。
まず Step 1 は次のように記されています。
Step 1: Register Test Lab’s custom URL scheme
- In Xcode, select a project target.
- Click the Info tab, then add a new URL type.
- In the URL Schemes field, enter firebase-game-loop. You can also register the custom URL scheme by adding it to your project’s Info.plist configuration file anywhere within the <dict> tag:
Firebase Test Lab上でテスト実行する上で、上記設定は必須です。Info.plist
に custom URL scheme
を加えるよう記載されていますね。これは
IPostprocessBuildWithReport.OnPostprocessBuild
にてビルド後にカスタム処理を加えることで実現できます。具体的には以下のようなコードになります。
#if UNITY_EDITOR
using System.IO;
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
#if UNITY_IOS
using UnityEditor.iOS.Xcode;
using UnityEditor.iOS.Xcode.Extensions;
#endif
namespace Firebase.TestLab
{
public class BuildProcessor : IPostprocessBuildWithReport
{
public int callbackOrder { get { return 10; } }
public void OnPostprocessBuild(UnityEditor.Build.Reporting.BuildReport report)
{
#if UNITY_IOS
// Custom URL scheme for Firebase Test Lab
// https://firebase.google.com/docs/test-lab/game-loop
var path = Path.Combine(report.summary.outputPath, "Info.plist");
var doc = new PlistDocument();
doc.ReadFromFile(path);
var urlTypes = doc.root.CreateArray("CFBundleURLTypes");
var dict = urlTypes.AddDict();
dict.SetString("CFBundleURLName", "");
dict.SetString("CFBundleTypeRole", "Editor");
var urlSchemes = dict.CreateArray("CFBundleURLSchemes");
urlSchemes.AddString("firebase-game-loop");
doc.WriteToFile(path);
#endif // UNITY_IOS
}
}
}
#endif // UNITY_EDITOR
つぎに、 Step 2 の記載を確認します。
Step 2 (optional): Configure your app to run multiple loops
If your app has multiple custom URL schemes registered and you plan to run multiple loops (aka scenarios) in your test, you must specify which loops you want to run in your app at launch time.
optional
と記載されている通り、Step 2の対応はテスト実行に必須ではありません。そのため本稿では扱いませんが、もしStep 2を組み込んでテスト範囲を選択可能するのであれば、Unity公式ドキュメントの
Enabling deep linking
を参考に、Application.deepLinkActivated
にカスタム実装を加えることになります。
そして
Step 3
のテスト実行に入る前に、
End a test early
について解説します。これは実用上必須なもので、下記引用分にある通り、正しくテストが終了したことをFirebase Test Labに知らせるため custom URL scheme
の firebase-game-loop-complete
呼び出しが必要です。この呼び出しがないと、テスト実行は失敗(タイムアウト)と処理されてしまうのでご注意ください。
また
Write custom test results
。これはログなどの出力を、後の参照のためにアップロードするもので、やはり実用上必須です。以下引用文にある通り、アプリ用のデータ保存パス内の GameLoopsResults
フォルダ下にあるファイル群がアップロード対象となります。
以下は、上記の終了処理とログ出力をC#で実装したものです。
googlecodelabs/unity-firebase-test-lab-game-loop
内の
Firebase.TestLab.TestLabManager
を継承する形で実装されているため、Codelabsの
に解説されているように、iOSTestLabManager
のインスタンスに対して、LogToResults()
と NotifyHarnessTestIsComplete()
を適宜callしてください。
using System;
using System.IO;
using UnityEngine;
namespace Firebase.TestLab
{
internal sealed class iOSTestLabManager : TestLabManager
{
public iOSTestLabManager()
{
string logPath = Path.Combine(Application.persistentDataPath, "GameLoopResults");
if (!Directory.Exists(logPath))
{
Directory.CreateDirectory(logPath);
}
logFile = Path.Combine(logPath, "results_scenario_0.json");
}
public override void NotifyHarnessTestIsComplete()
{
Application.OpenURL("firebase-game-loop-complete://");
}
public override void LogToResults(string s)
{
StreamWriter writer = new StreamWriter(logFile, true);
writer.WriteLine(s);
writer.Flush();
writer.Close();
}
private readonly string logFile;
}
}
ここで、ログファイルをJSONフォーマットとしたのは、Android用の実装である
Firebase.TestLab.AndroidTestLabManager
の挙動に合わせるためです。Codelabsの
からの引用になりますが、Androidテスト完了時のスクリーンショットから、ログの拡張子は .json
となっていることが確認できます。
- テスト端末を選択
Results
に出力したJSONファイルが表示され、選択すると内容が表示される
iOS版もAndroid版同様にJSONフォーマットであることを前提にログ出力することを推奨します。理由として Firebase Test LabのWeb UIはJSONデータのツリー構造付き表示に対応しており視認性が良いこと、またJSONファイルはParseが容易であり、複数端末でのテスト結果を集計するといった機械的処理に向いているためです。幸い、UnityでのJSON出力は JSON Serialization で簡単に対応することができます。
Firebase Test Labのテスト実行
Step 3
のテスト実行に戻ります。Firebase Test Labは、Webブラウザからipaファイルをアップロードし、端末をフォームから選択することでGUIベースのテスト実行が可能です。その際の具体的な操作手順は、Codelabsの
が、参考になるでしょう。ですが、前述した様にテスト実行のような定型処理は、gcloud CLIでスクリプト化することを推奨します。
以下、テスト実行のコマンド例になります。
gcloud alpha firebase test ios run --app $BITRISE_IPA_PATH --type=game-loop --device model=iphone8,version=12.0,locale=ja_JP
引数の解説です。まず --app
オプションで、XcodeでArchiveしたipaファイルのパスを指定し、--type
オプションで、Game Loop testであることを指定しています。次に --device
オプションで、テスト用端末のMODEL_ID
、iOSのバージョン、ロケールをそれぞれ設定しています。MODEL_ID
や対応するiOSのバージョンは、前述の gcloud firebase test ios models list
コマンドで確認してください。
そして、本CLIのコマンドは、GCP公式ドキュメントの
gcloud alpha firebase test ios run
にて、以下の記載がある(※本稿執筆時点)通り、gcloud
コマンドに続き alpha
の指定が必要なことにご注意ください。
(ALPHA)
gcloud alpha firebase test ios run
invokes and monitors tests in Firebase Test Lab for iOS.
テスト実行コマンドの重要なオプションを2点紹介します。
-
--no-record-video
動画での記録をOFFにします。テスト実行の確認がテキストログで完結するものは、このオプションを指定した方がストレージの節約になるでしょう。
-
--results-bucket
テスト実行結果の出力先を、GCS(Google Cloud Storage)のバケット名で指定します。ログを機械的に処理する場合は、このオプションで出力先を固定しましょう。なおGCS上のファイルは、
gsutil
コマンドで取得可能です。
以下のスクリーンショットは、Unity製iOSアプリのFirebase Test Lab実行例になります。
まとめ
以上、Unity製iOSアプリでFirebase Test Labを使うための手順を解説いたしました。Firebase Test Lab自体は、
Firebaseの料金プラン
によると無料のSpark プランでも、物理デバイスでのテストが最大5件/日実行できるとのことなので、是非試してみてはいかがでしょうか?他のクラウドサービスも同様だと思いますが、Firebase Test Labは本稿執筆時点で iPhone 11 Pro
や Pixel 4
といった、実売価格にして8万超のハイエンド端末を使うことができます。そのため、実機を代替することが端末購入前に実証できれば、コスト削減になるでしょう。
ところで本稿では書ききれなかったノウハウも残っています。実際のプロダクト開発では、Android向けに追加実装していますし、JSONベースのProfiling機能を付けたり、 Cysharp/RuntimeUnitTestToolkit を活用して Play Mode test をFirebase Test Lab上で動かすといった工夫を行っています。これらについては、いつかまた別の機会に発表できたらと思います。
最後に、今後のアップデートはTwitter アカウント @DeNAxTech にて情報発信されると思いますので、こちらの記事が面白かったと思う方はぜひフォローをお願いします!
最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。