こんにちは。てぃろです。
大規模ソフトウェア開発の品質を考える第三弾です。第一弾と第二弾はコチラから。
今回はiOSアプリの品質を確保するために必要なUIテストについての記事です。この記事で言いたいことは以下の3点です。
- XCUITestは便利なので、積極的に活用してほしい
- fastlaneを組み合わせて使うと、複数端末のスナップショット撮影を1コマンドで実行できる
- ただし、全部は自動化できないので、自動化できるところとできないところを見極める必要がある
また、本文では自動テストにおいて重要な箇所の実装は紹介しますが、すべてを書きません。既に実装について優れた記事は他にあるので、そちらを参照いただければよいかなと思います。
iOSアプリのUIテストをしたい理由は、UIのデグレ検知
当然ですが、本記事で扱う課題はiOSアプリをUIテストしたいけど大変だからどうにか実施コストを下げたいというものです。
今回は特にUIのデグレを検知したいというのが最大のモチベーションです。
デグレとは、デグレードの略で、ある修正が影響して修正していない別の箇所が仕様とは異なる挙動をしてしまうようになる、という現象を指します。
つまり、ある機能や文言の修正によって画面が崩れていないかをチェックしたいのです。
iOSアプリであったとしても大規模になると画面数はかなり多いので、何か修正がマージされるたびに画面崩れをすべての画面でチェックするなどいくら工数があっても不可能です。
それを数種類の画面サイズの違う端末全てチェックするというのですから、なおさら不可能です。
しかしiOSアプリは審査が厳しいのでしっかり対策していかないと審査を落とされて予定通りにリリースできないという危険性も否定できません。
そこで、複数端末のUIテスト(特にデグレ検知)を自動化できないか?と考えたわけです。
iOSアプリのUIテストといえばXCUITest、でも複数端末でデグレ検知するにはひと手間必要
iOSアプリのUIテストフレームワークとして、公式にXCUITestというものが存在します。
XCUITestが初登場したのはWWDC2015でした。当時のWWDCの発表動画はこちらです。
動画ではXCUITestの基本的な使い方のデモを紹介しているので、XCUITestについて知りたい方はこちらの動画を一度見てみることをオススメします。
このように、Xcodeに統合されたUIテストフレームワークが公式で提供されており、特に何か理由がない限り、これを使っておくのが一番安全であると思います。
ここでいう安全とは、Swifの言語仕様変更に強いという意味です。こういったテストフレームワークで一番危険なのは、テスト対象となっている言語の仕様が変更されてテストが動作しなくなることです。その点、公式で提供される場合には、Swiftの言語仕様の変更とともにフレームワークの仕様も変更されるためフレームワークが動作しなくなるということはないでしょう。
また、XCUITestはテスト機能も充実しており、特にテストで必要な以下2点の機能を有しています。
- Assertion:画面上の文言や要素とその状態が期待する通りに存在することを確認できる(テストの確認ポイント)
- Recording:自動的に動作したUIテストの画面を録画できる(エビデンスの取得)
UIテストとしては十分な機能であると言えますが、今問題としているのは複数端末のテストにおけるUIのデグレ検知なので、これでは足りません。
複数端末のテストのUIテストのデグレ検知をするにはさらに以下の機能が必要です。
- Multi-Device:指定した複数の端末で同一のテストを自動で実行する
- Diff:画像の差分を検知する
XCUITestではこれらの機能は存在していません。そこで別のツールを活用していく必要があります。
複数端末のUIテストのデグレ検知を自動化する
先に使用するツールを書いてしまうと、Multi-Deviceに対してはfastlaneを、Diffに対してはBeyond Compareを使用していきます。
これらを使い、以下のようなテストフローを構築することを目指します。
UIテストの実行とともにデグレを確認したい画面のスクショを保存しておき、それを用いて画像差分を検知します。正解スクショは過去のUIテスト実行で得たスクショの中で正常なものとして保存しておいたスクショのことです。画像差分はこの正解スクショと新たに得たスクショの比較で実行します。
ここからは、上記のフローを作るために重要なfastlaneとBeyond Compareについて解説します。
fastlaneで複数端末のUIテストを自動化する
fastlaneはiOSアプリやAndroidアプリのテストや配信の自動化をしてくれるOSSです。
fastlaneには、snapshotというコマンドで複数種類のシミュレータを使って一気にUIテストを実行できる、という優れものです。公式のドキュメントはコチラから。
fastlaneのインストールはコチラを参照して実施して下さい。
fastlaneの初期設定
ここからは設定の手順を私の経験で捕捉しながら、日本語で手順を記載します。
- XcodeプロジェクトにUIテストのターゲットを作成する
- 既に存在していれば作成する必要はありません
- ターミナルを開き、
fastlane snapshot init
をプロジェクトディレクトリ内で実行します- すると、
SnapshotHelper.swift
とSnapfile
いう2つのファイルが作成されますが、この時点ではXcodeのプロジェクトに含まれるファイルとして認識されていません
- すると、
SnapshotHelper.swift
をプロジェクトの任意のディレクトリに追加する- プロジェクトルート直下に
fastlane
というディレクトリを作成しその配下にSnapfile
を配置する- 設定内容は後述します
- UIテストのターゲットに含めたXcode Schemaを新たに作成する
- 既存のSchemaを編集してもよいですが、fastlane実行用として別に作ることをオススメします
- Edit Schemaを選択し、Buildの設定でUIテストのターゲットについてTestとRunのチェックを入れる
- 同画面で、Sharedにもチェックを入れる
- テストコードの
setUp()
でsetupSnapshot(app)
を含めるように修正する- 公式ドキュメント通りに修正してOKです
Snapfileの設定(自動UIテストの設定)
Snapfileの設定内容ですが、まずはその全貌をお見せしておきます。
# Uncomment the lines below you want to change by removing the # in the beginning
# A list of devices you want to take the screenshots from
devices([
"iPhone 8",
"iPhone 8 Plus",
"iPhone 11 Pro Max",
"iPhone 12",
"iPhone 12 Pro Max",
"iPhone 12 mini",
"iPhone SE (1st generation)",
])
languages([
"ja",
])
# The name of the scheme which contains the UI Tests
scheme("UITest")
# Where should the resulting screenshots be stored?
output_directory("./fastlane/screenshots")
# remove the '#' to clear all previously generated screenshots before creating new ones
clear_previous_screenshots(true)
# Remove the '#' to set the status bar to 9:41 AM, and show full battery and reception. See also override_status_bar_arguments for custom options.
override_status_bar(true)
# If the logs generated by the app (e.g. using NSLog, perror, etc.) in the Simulator should be written to the output_directory
output_simulator_logs(true)
# Should an Xcode result bundle be generated in the output directory
result_bundle(true)
# Arguments to pass to the app on launch. See https://docs.fastlane.tools/actions/snapshot/#launch-arguments
# launch_arguments(["-favColor red"])
上記で有効になっている設定はすべて設定を修正したものです。
devices
:使用可能なシミュレータを調べて記載します。シミュレータはxcrun simctl list
コマンドを使って一覧することができます- 上記のシミュレータはXcode 13 betaで使用可能なシミュレータであり、すべてiOS15で動作します
- テストを効率的に進めるためにどの端末を選べばよいかは後述します
schema
:作成したUIテスト用のSchema名を指定しますoutput_directory
:スクリーンショットの出力先です。任意のディレクトリを指定してくださいclear_previous_screenshots
:fastlaneでテスト実行前に既に存在しているスクショを削除するかを決める設定です。正解データをきちんと残しておくような運用も組むと考えるとtrue
として毎回の実行でスクショを削除しておくのがよいと考えましたoverride_status_bar
:画面上部の時間を固定する設定です。デグレ検知のためには必須でtrueにしておきますoutput_simulator_logs
:ログ出力の設定です。長時間のテスト実行になるのでできるだけログはしっかり出しておきたいですresult_bundle
:XCTestと同様にテスト結果の出力としてResultBundleを生成するかどうかの設定です
ここまでで、fastlaneを使ってUIテストを実行するための設定は整いました!あとは、スクショ撮影のメソッドをテストコードの各所に書けば準備は完了です。
なお、XCode上で該当のテストケースをdisableにしてもfastlaneではその設定は無視されてしまいますので、fastlaneで実行したくないテストがあればコメントアウトするようにしましょう。
効率的なUIテストを進めるための端末の選び方
上記設定の devices
では、どの端末をテストに採用するべきか、という検討をすることが不可欠です。使用可能なシミュレータ全部で実行することは現実的に不可能だからです。
そこでiPhoneの画面の仕様について確認しましょう。iPhoneの画面描画を決定づけているのはポイントという考え方です。
詳細な解説は解説記事に譲りますが、iPhoneではRetinaディスプレイの解像度が非常に細かすぎるため、ハード上でのピクセル数とソフトウェア上でのピクセル数が厳密に1:1にならないことから理論的な画面解像度のようなものを定義しており、それをポイントと言います。
つまり、このポイントが同じものは描画が同じになるので、ポイントのバリエーションの分だけ端末を選べばよい、ということになります。
ここから整理すると、2021年8月時点でiOS15対応のiPhoneとしては以下の端末を選定しておくのがよいでしょう。
- iPhone 8
- iPhone 8 Plus
- iPhone 11 Pro Max
- iPhone 12
- iPhone 12 Pro Max
- iPhone 12 mini
- iPhone SE (1st generation)
複数端末の自動UIテストを実行する
ここまでの準備がすべて整っていれば、あとはコマンドを一つ実行するだけで複数端末の自動UIテストを実行することができます。
ターミナルを起動して、プロジェクトルートに移動し、以下のコマンドを実行します。
fastlane snapshot
ここからはfastlaneがすべて自動で進めてくれます。
実行中はシミュレータが画面に立ち上がることはありませんが、画面のスクショは順調に撮影されます。またUIテストの性質上実行時間が非常に長くなることが多いので、Macを放置できる十分な時間を確保してから実行されることをオススメします。
テストが問題なく完了すれば、先ほど Snapfile
に設定したディレクトリに画像を一覧するためのHTMLと画像ファイル(PNG)が出力されています。
次に、デグレ検知をするためにここで出力されたPNGファイルを使います。
Beyond Compareでデグレ検知を半自動化する
次に、デグレ検知のために画像比較をします。
前述の通り、複数端末の自動UIテストが完了すると、PNGでスクショが大量に作成されます。
それらすべて同一のファイル名の画像同士で差分を取り、差分がある画像についてのみ人間の目で確認する、ということをできるようにします。
さすがにどこにどんな差分が出てしまったかは人間の目で確認する必要がありますが、違いがないところを見なくていいようにする、という意図です。
このために、今回は Beyond Compare というツールを使います。これは有料ツールですが、無料で30日間は起動可能なので十分機能を試すことができます。無料試用はコチラから。
このツールのよいところは、ディレクトリ同士を比較して差分があるファイルを表示することができることです。複数端末の自動UIテストで出力される画像は大量にあるので、それらの画像を一枚ずつ手作業で比較することはしたくありません。そこでディレクトリ同士を比較し同一ファイル名のファイル同士を比較して差分があるものをハイライトしてくれる、という機能が必須となるわけです。
その点で、Beyond Compareはディレクトリ同士を比較することができますので、今回の用途に適しています。ただ、残念ながらBeyond Compareはコマンドラインツールがないようなのでデグレ検知の実行だけは手動で行う必要があり、完全自動化とはなりませんでした。
でも、大量の画像を一つ一つ比較しなくていいと思えば安いものかなと私は思っています。
完全自動化したいなら…WinMergeを使おう
ここまで見た通り、デグレ検知は一つだけ手作業が入ってしまっているので、完全自動化とはいきませんでした。
しかしWindows専用ですがWinMergeも同様にディレクトリ同士の比較と画像比較ができるツールがあり、こちらを使えば完全自動化できる可能性があります。
WinMergeにはコマンドラインツールもありますので、ファイルの移動など多少の作りこみをすれば、デグレ検知そのものまで完全自動化できる可能性はあります。
大量に出力された画像をどのようにしてWindowsに共有するか?という課題が残りますが、それさえクリアできれば自動化できるという意味で有力な選択肢になるのではないでしょうか。
自動化できないテストは手動でやろう
ここまで見てきた方法は万能というわけではありません。それはXCodeに付属するシミュレータで実行していることに起因するため、シミュレータの制約がそのままこの自動テストの制約となっているという意味です。
具体的には、ウォレットなどのハードに依存する機能が使えないケースです。
そのようなケースだけは実機で実行するようにしましょう。逆に言えば自動化によって手動で実施するべきケースはそれらだけになった、とも言えます。
アクセシビリティ設定を変更した場合のテストはシミュレータを増やすのがよさそう
iOSはアクセシビリティ設定で、OSレベルで画面表示を変更することができます。
そのような変更を受けたときでも画面が崩れないようテストをし続けるとなれば、その設定の分だけUIテストの数は増えます。そのようなときにこそ自動UIテストが役に立つと思います。
シミュレータで実行するとしても、やはりアクセシビリティの設定を個別に設定する必要はあります。ただ残念ながらシミュレータのアクセシビリティの設定をコマンドラインで変更することはできないようなので、一つ一つシミュレータを起動して設定を変更する必要があることを意味します。
テストのたびに毎回そんなことはしていられないので、いっそのことアクセシビリティ設定を入れたシミュレータというのを新たに追加しておけばよいのではないでしょうか?
これはまだ実際に実行したわけではなく、調べた限りなので、多少想像が入っていますが、設定の手間を考えればこの方向性で検討するのが良いのではないかと思います。
テストコストを下げる工夫をしよう
ここまで自動でテストを実行する方法について解説してきて、その中でUIテストは実行コストが非常に大きいものです、という話を何度もしてきました。ここまでで言う実行コストはMacを定期的に監視する必要がある手間のことと言いかえられます。
しかしUIテストを実行するには、UIテストのためのテストコードを書く必要があり、その作成コストも気にする必要があります。
また、UIテストは実行時間がかかる、と言っているだけではなく、その実行時間そのものもいかに減らすかが効率的な自動テストを維持していくためには不可欠な考え方です。
これらを実行していくための工夫は、以下のスライドが非常に参考になります。
このスライドの中で紹介されている方法がすべて実行できるわけではないですし、必ずしもそのプロジェクトで必要とされる対策ではないこともあると思います。
これらの中から各々の環境にあった方法を活用することにしていきましょう。
まとめ
複数端末の自動UIテストの実行方法について解説してきました。
しかし、世の中にはAWSのDeviceFarmやAzureのAppCenterなどの複数の実機でUIテストを実行できるとするSaaSは既に存在しています。しかしそれらは最新のXCodeに対応していない事が多いです。特にプロジェクトの特性上、Xcodeのbeta版を扱う必要がある場合には、それらのSaaSは全く役に立ちません。
そこで、iOSアプリの自動テストではまだまだ自身で自動実行環境を作ることが必要になり、今回こうして構築するに至りました。
また、本文では触れませんでしたが、紹介した自動UIテストを自動的に実行する方法もいくつかあります。
CI/CDで組み込みたいならGitLab RunnerをMacに仕込んでも良いでしょう。シェルを作ってcronを設定してみてもいいかもしれません。
そういった実行の自動化も含めて本記事で説明してきた内容は、Xcode Serverを使えばより高度に手軽に実行できると思います。今回の私の環境ではXcode Serverは使うことができなかったので、本記事のような方法を見出しましたが、そうではないならXcode Serverの利用をまず第一に検討されてみてもいいのではないでしょうか。
おまけ
デグレ検知のために画像差分を比較する必要がありましたが、シミュレータでも時刻は変化していたので、それが差分として必ず出てきてしまうことがありました。
上記の説明ではこれをfastlaneの設定で防ぎましたが、それをする前に検討したことがありました。これは別記事にしましたので、こちらも参考までにご覧ください。