DeNA Testing Blog

Make Testing Fun, Smart, and Delighting End-Users

ゼロ円から始めるクラウドの実機を使った自動テスト(iOS)

この記事はDeNA Advent Calendar 2018 その1の2日目の記事です

こんにちは。SWETの加瀬(@Kesin11)です。

2018年も残すところ一ヶ月弱となりましたが、今年一番のニュースと言えば何だったでしょうか? そう、Google IO 2018で発表されたFirebase Test LabのiOS版ですね!

Firebase Test LabはiOSとAndroidに対応した実際の実機デバイス上でテストを実行してくれるサービスです1
Android版は結構前から既に提供されており、Android Studioにも統合されているので使ったことがあるという方も結構いるのではないでしょうか?

一方でiOSに関してもFirebase Test Labと同様の実機デバイス上でテストを実行してくれるサービスはいくつかありますが、Firebase Test Labが将来的にiOSをサポートするかどうかという話はこれまで何もありませんでした。

そのような状況でしたので、Google IO 2018でFirebase Test LabのiOS版が発表されたとき個人的には大興奮でした。そして先日開催されたFirebase Summitでついに、βが取れて正式にリリースされたというアナウンスがありました🎉

iOS版ではテスト用のビルドをアップロードすると、Firebase Test LabにあるiOS実機でXCTest、XCUITestを実行してくれて、デバイスごとのテスト結果や動画などを確認できます。

前置きが長くなりましたが、今回はそんな待望のFirebase Test LabのiOS版について、以下の複数の利用方法についてそれぞれ解説していきます。

  • Bitriseから使う
  • gcloudコマンドから使う(ローカル)
  • gcloudコマンドから使う(CI環境)
  • Fastlaneプラグインから使う

なお、本記事の動作確認はこちらのリポジトリをベースにXCTestを追加し、XCUITestを少し修正したものを使用しています。
使用したXcodeのバージョンは10.1です。
https://github.com/browserstack/xcuitest-sample-browserstack

Bitriseから使う

Bitriseはモバイルアプリに特化したCI/CDサービスです。
もし既にBitriseをお使いであれば、現状ではこの方法が最も簡単、かつ無料の方法になります。

BitriseにはiOS Device Testingというステップが存在します。
これは名前の通りに実機デバイスでXCTest、XCUITestを実行し、テスト結果だけではなくてそのときのスクリーンショット、動画、デバイスログも保存してくれるものです。実は、その裏側はFirebase Test Labを使用していることが公式に解説されています。

Firebase Test Labは本来アカウントの認証が必要であったり、一定以上の使用には課金が必要です(認証と料金については後述)。 しかしiOS Device Testingを使用する場合にはFirebaseとのやりとりをBitriseがやってくれるため、アカウントの認証不要、さらに現在はβ版のためか無料で使うことができます。

使用可能なデバイスの一覧はiOS Device Testingのステップから確認できます。執筆時点での一覧はこのようになっていました。

f:id:swet-blog:20181130104530p:plain
Bitrise iOSデバイス一覧

さすがに最新のiPhone XSなどはないものの、iOS 12のiPhone Xは存在しています。 古めのデバイスも結構揃っており、最も古いものでiPhone 6が存在し、多少ですがiPadも種類が用意されています。さらにiOSのバージョンはiOS 9.0からそろっています。
さすがに全デバイス × パッチバージョンまで含めた全iOSバージョンの全てをカバーしているわけではありませんが、これだけのデバイス × iOSバージョンの実機を実際に手元で用意、管理するのはなかなか大変だと思います。これこそが、この手のサービスで最も嬉しいポイントではないでしょうか。

iOS Device Testingのさらに詳しい使い方やスクリーンショットは、Bitrise公式のドキュメントやブログが充実していますので、そちらを見てもらうと分かりやすいと思います。

Device testing for iOS
Introducing solid and snappy real device testing for iOS with Firebase (beta)

gcloudコマンドから使う(ローカル)

Bitriseを使っていない場合、gcloudコマンドから直接Firebase Test Labを使う方法になります。

https://firebase.google.com/docs/test-lab/ios/command-line?hl=en

Firebase Test Labを使うにはアカウントの認証と、紐付けるプロジェクトの選択が必要になります。
まずはドキュメントに従ってgcloud auth logingcloud config set project PROJECT_IDを実行してください。PROJECT_IDはfirebaseのプロジェクトIDです。もしまだfirebaseのプロジェクトを作成していない場合、先にfirebaseのプロジェクトを作成しておいてください2

gcloudコマンドのセットアップができたら、まずは使用できるデバイスの一覧を見てみましょう。

$ gcloud firebase test ios models list
┌──────────────┬───────────────────────┬─────────────────────┬─────────┐
│   MODEL_ID   │          NAME         │    OS_VERSION_IDS   │   TAGS  │
├──────────────┼───────────────────────┼─────────────────────┼─────────┤
│ ipad5        │ iPad (5th generation) │ 11.2,12.0           │         │
│ ipadmini4    │ iPad mini 4           │ 11.2,12.0           │         │
│ ipadpro_105  │ iPad Pro (10.5-inch)  │ 11.2                │         │
│ iphone6      │ iPhone 6              │ 11.4                │         │
│ iphone6s     │ iPhone 6s             │ 10.3,11.2,11.4,12.0 │         │
│ iphone6splus │ iPhone 6s Plus        │ 9.0,9.1             │         │
│ iphone7      │ iPhone 7              │ 11.2,11.4,12.0      │         │
│ iphone7plus  │ iPhone 7 Plus         │ 11.2,11.4,12.0      │         │
│ iphone8      │ iPhone 8              │ 11.2,11.4,12.0      │ default │
│ iphone8plus  │ iPhone 8 Plus         │ 11.2,11.4,12.0      │         │
│ iphonese     │ iPhone SE             │ 11.2,11.4,12.0      │         │
│ iphonex      │ iPhone X              │ 11.2,11.4,12.0      │         │
└──────────────┴───────────────────────┴─────────────────────┴─────────┘

先述のBitriseと同じリストが表示されましたね。では次にテスト用のビルドを作成します。

ドキュメントにあるようにxcodebuildでビルドします。 アプリ名とスキームがMyAppで、ビルド結果の出力先をfirebase_testとした場合には以下のようなコマンドになります。

$ xcodebuild -project MyApp.xcodeproj \
  -scheme MyApp \
  -derivedDataPath firebase_test \
  -sdk iphoneos build-for-testing

さらにFirebase Test Labでテストを実行するには、テスト用のビルド成果物とxctestrunファイルを1つのzipに圧縮する必要があります。

$ cd firebase_test/Build/Products
$ zip -r MyTests.zip Debug-iphoneos MyApp_iphoneos12.1-arm64e.xctestrun

あるいはFastlaneを導入済みならばscanbuild_for_testingオプションを使ってテスト用ビルドを生成しても大丈夫です。

scan(
  scheme: 'MyApp',
  clean: true,
  skip_detect_devices: true,
  build_for_testing: true,
  sdk: 'iphoneos',
  should_zip_build_products: true
)

テスト用のビルドができたらいよいよテストを実行してみましょう。

$ gcloud firebase test ios run --test MyTests.zip \
  --device model=iphonex,version=12.0,locale=ja_JP \
  --device model=iphone8,version=11.4 \
  --device model=iphone6splus,version=9.1 \
  --device model=iphone6s,version=10.3,locale=ja_JP,orientation=landscape \
  --device model=ipad5,version=12.0 \
  --xcode-version=10.1

テストを実行したいデバイスの設定を--deviceの引数に渡します。modelとversionには先ほどのモデル一覧で表示されたMODEL_IDとOS_VERSION_IDSを指定します。必要であればlocaleとorientationも指定可能で、指定しなかった場合のデフォルトはenとportraitとなります。
指定可能なlocaleについてはgcloud firebase test ios locales listで一覧の確認が可能です。

--xcode_versionはFirebase Test Lab側でテストを実行するXcodeのバージョンのようです。執筆時点ではXcode 10.1でビルドした場合に--xcode_versionの指定無しでも動くことを確認しましたが、古いXcodeを使用している場合や、新しいXcodeが出た直後などにはバージョンを指定しておいた方がいいかもしれません。

特にエラーなくコマンドが実行できたら、まだテスト実行中の段階からFirebase Test Labのページで結果を見ることができます。
実行が完了するとこのように複数のデバイスでテストケースごとのテスト結果、スクリーンショットや動画を確認できます。

f:id:swet-blog:20181130104903p:plain
デバイス毎の実行結果
f:id:swet-blog:20181130104911p:plain
テストケースの結果

gcloudコマンドから使う(CI環境)

ローカルの環境から実行できることは確認できましたが、アプリのビルドやテストはローカルとは別のCI環境で実行していると思いますので、継続的にテストを実行させるにはCI環境からFirebase Test Labを実行をできるようにしておくのがよいでしょう。

しかし、先述のgcloud firebase test iosを実行するにはアカウントの認証が必要です。そして認証にはブラウザでログインする必要があるので通常の方法ではCI環境で認証することはできず、テストを実行できません。

この問題はサービスアカウントを使って認証することで解決できます。この方法に関してのドキュメントはAndroid版の方に一応存在するのですが、あまり分かりやすいとは言えないため解説します。

サービスアカウントによる認証

まずはGCPのコンソールからサービスアカウントを作成します。
https://console.cloud.google.com/iam-admin/serviceaccounts/

サービスアカウントの権限には「プロジェクトの編集者」の権限を付ける必要があります。

f:id:swet-blog:20181130105326p:plain

権限の設定が完了したら最後にjson形式で鍵を生成してください。

次にGoogle Developers Console API Library pageからGoogle Cloud Testing APICloud Tool Results APIを検索し、両方とも有効化してください3

jsonの鍵を用意できたらgcloudコマンドがサービスアカウントを使うように認証させます。

$ gcloud auth activate-service-account --key-file YOUR_SERVICE_ACCOUNT_KEY.json

gcloud auth listを実行してサービスアカウントにACTIVEが付いていればOKです。

この状態で再度gcloud firebase test ios runを実行して、まずはローカルの環境で実行できることを確認しておきましょう。

(サービスアカウントの認証がうまくいかない場合)

自分も実際に遭遇したのですが、上記の手順通りに作成したサービスアカウントでもなぜかFirebase Test Labを実行するときにエラーとなってしまう場合があるようです。
https://stackoverflow.com/questions/48327027/cant-run-firebase-test-lab-tests-using-gcloud-and-service-account-403-does-no?rq=1

上記のベストアンサーではない方の回答に、Firebaseのサービスアカウントの鍵を使うと動作したという回答がありました。Firebaseのサービスアカウントの鍵は以下のurlからアクセスできるページで作成できます。
https://console.firebase.google.com/u/0/project/FIREBASE_PROJECT_ID/settings/serviceaccounts/adminsdk
(FIREBASE_PROJECT_IDのところは自分のプロジェクトidに書き換えてアクセスしてください)

自分が試したところ、たしかに動作することを確認しました。詳しい理由は不明ですが、自分と同じようにGCPで作成したサービスアカウントではどうしてもエラーになってしまう場合に試してみるといいかもしれません。

CIサービスのビルドフローの設定

サービスアカウントの認証ができたところで、次はCIサービスのビルドフローに組み込んでみましょう。

お使いのCIサービスでgcloudコマンドを使えるようにし、先ほどと同様にサービスアカウントで認証をしてからテストを実行するフローを構築してください。少し注意が必要なのは、サービスアカウントの鍵のjsonです。 チームの体制にもよりますが、一般的にはリポジトリにコミットするべきではないでしょう。しかし、使用するCIの環境によっては秘密にしておきたいファイルをリポジトリとは別に登録するという方法ができない場合もあるかと思います。
そのような場合は、CircleCIのドキュメントにあるようにjsonの中身を環境変数として登録しておき、ビルドフローの中で再度jsonとして書き出すという方法が使えます。
Language Guide: Android - CircleCI

実際のサンプルとしてBitriseでgcloudを使ってサービスアカウントの認証、テストを実行する方法を紹介します。

まず、サービスアカウントの鍵であるjsonの中身をcatとpbcopyなどを使って丸ごとコピーして環境変数にセットしておきます。以下のコードでは$SERVICE_ACCOUNT_KEYで参照しています。

BitriseのScriptステップはシェルスクリプトをそのまま実行できるため、ここでインストールから認証まで行います。

#!/usr/bin/env bash

# Bitriseはデフォルトでgcloudがインストールされていないため、インストールから行う
curl https://sdk.cloud.google.com | bash > /dev/null 2>&1

# gcloudコマンドが使えるようにPATHを通す
source "${HOME}/google-cloud-sdk/path.bash.inc"
gcloud version

# サービスアカウントで認証
## 環境変数からjsonに復元する
echo ${SERVICE_ACCOUNT_KEY} > "${HOME}/gcloud-service-key.json"
gcloud auth activate-service-account --key-file "${HOME}/gcloud-service-key.json"
gcloud auth list

# プロジェクトidを設定
gcloud config set project ${GCLOUD_PROJECT}

後はテスト用のビルドを作成し、gcloud firebase test ios runを実行するだけです。テスト用のビルドについては説明済みですので、今回は省略します。

#!/usr/bin/env bash

# BitriseのScriptステップはPATHが引き継がれない(?)ようなので再度PATHを通す
source "${HOME}/google-cloud-sdk/path.bash.inc"

# Xcode Build for testing for iOSのステップでビルドすると$BITRISE_TEST_BUNDLE_ZIP_PATHというパスにzipされた成果物が保存されている
gcloud firebase test ios run --test "${BITRISE_TEST_BUNDLE_ZIP_PATH}" \
  --device model=iphone8,version=11.4 \
  --device model=iphonex,version=12.0,locale=ja_JP \
  --device model=iphone6splus,version=9.1 \
  --device model=ipad5,version=12.0 \
  --device model=iphone6s,version=10.3,locale=ja_JP,orientation=landscape

上記のサンプルには主にgcloudコマンドをセットアップするBitrise特有のコードも含まれてしまっていますが、サービスアカウントで認証してテストを実行する部分は他のCI環境でも同様に動作すると思います。

もしもより詳しいドキュメントを探す場合は、前例の多いAndroid版のFirebase Test Lab + CircleCIの組み合わせの記事を探してみるのがよいでしょう。

Fastlaneプラグインから使う

もしFastlaneを導入済みであれば、Firebase Test Labを実行するFastlaneプラグインを使用するという選択肢もあります。
fastlane/fastlane-plugin-firebase_test_lab

使い方はREADMEに書いてあるように、scanでテスト用のビルドを行ってからfirebase_test_lab_ios_xctestでテストを実行します。ただし注意点として、READMEのサンプルをそのまま使用した場合はgcloudのデフォルトの認証情報が使われます。つまりgcloudのコマンドから実行したときと同様に、CI環境では動きません。
oauth_key_file_pathというオプションにサービスアカウントの鍵のパスを指定することで、サービスアカウントで認証を行うことができます。

# fastlane-plugin-firebase_test_labのREADMEから転載
scan(
  scheme: 'YourApp',                  # XCTest scheme
  clean: true,                        # Recommended: This would ensure the build would not include unnecessary files
  skip_detect_devices: true,          # Required
  build_for_testing: true,            # Required
  sdk: 'iphoneos',                    # Required
  should_zip_build_products: true     # Must be true to set the correct format for Firebase Test Lab
)
firebase_test_lab_ios_xctest(
  oauth_key_file_path: '/your/servie_account/path.json', # このオプションにサービスアカウントの鍵のjsonのパスを指定する

  gcp_project: 'your-google-project', # Your Google Cloud project name
  devices: [                          # Device(s) to run tests on
    {
      ios_model_id: 'iphonex',        # Device model ID, see gcloud command above
      ios_version_id: '11.2',         # iOS version ID, see gcloud command above
      locale: 'en_US',                # Optional: default to en_US if not set
      orientation: 'portrait'         # Optional: default to portrait if not set
    }
  ]
)

ただし注意点として、FastlaneのプラグインはコミュニティによってメンテされているためXcodeのバージョンが上がった場合などに動かなくなる可能性があります。
実際に、Xcode 10.1でxctestrunの形式が微妙に変わったことで動かなくなってしまったバグの修正PRがついこの前マージされたようです。 このように何かがバージョンアップされた場合に壊れてしまうリスクがあるということは覚えておいた方がいいでしょう。

料金

最後は気になるお値段の方ですね。

まず、Bitriseで実行した場合は現在のところ無料です🎉
おそらく現在はまだβであることが無料の理由だと思いますので、今後どうなるかはBitrise Blogなどをウォッチしておくといいでしょう。

Bitriseを使わずに直にFirebase Test Labを使用する場合は無料プランと、従量課金プランがあります。

まず、無料のSpark Planの場合はテスト5回/日のようです。チームの規模が大きい場合には全てのpull-reqごとに実行するような使い方は難しいと思いますので、テストを実行するブランチを絞ったり、深夜ビルドでのみ実行するといった工夫が必要かもしれません。

従量課金制のBlaze Planにした場合、回数の上限はなくなり$5/デバイス/時間となります。これの意味するところは、テストを実行したデバイス全ての稼働時間の合計が1時間につき$5という意味です。 具体的な例としては、1回の実行に12分かかるテストを5台のデバイスで実行した場合、合計で1時間=$5と計算されるということになります。

ちなみに、料金はVirtual DeviceとPhysical Deviceに分かれていますがVirtual Deviceは今のところAndroidだけ存在しており、iOSの場合はPhysical Deviceのみとなります。

上記の料金の計算は執筆時点(2018年12月)のドキュメントを参考にしたものであり、必ずしも正しいことを保証するものではありません。
また、Firebaseの料金は今後変化する可能性がありますので詳しくはFirebaseの料金のページを参照するようにしてください。

まとめ

Bitrise、gcloudコマンド、FastlaneのそれぞれでFirebase Test Labによる実機テストを行う方法の解説をしました。Firebase Test Labはとても簡単に使えて、無料プランもあるのでこの手のサービスの中ではかなり試しやすいサービスだと思います。

ビルドのたびに様々なiOSデバイス、iOSバージョンの実機で自動テストを実行することが理想ですが、人手を介さずにそれを全自動で行ってくれるシステムを構築・運用することはとても難しいです。 そのようなシステム構築やデバイスの管理をする必要がないFirebase Test Labを使って、ゼロ円から実機の自動テストを始めてみませんか?


  1. Android版では実機だけではなくエミュレータを使用してテストを実行することも可能です。

  2. 実はGCPのプロジェクトIDでも実行することは可能ですが、テスト結果画面を見るにはFirebaseのプロジェクトページにアクセスする必要があるため、どのみちGCPのプロジェクトに紐づけたFirebaseのプロジェクトを作成する必要があります。

  3. ここで有効化しなかった場合でもサービスアカウントで認証後にTest Labを実行するとAPI [toolresults.googleapis.com] not enabled on project [****]. Would you like to enable and retry (this will take a few minutes)? (y/N)? というように有効化するか尋ねられるので、そこでyesとしても大丈夫なようです。