FURYU Tech Blog - フリュー株式会社

フリュー株式会社の開発者が技術情報を発信するブログです。

MagicPodでiOSアプリのリグレッションテストを自動化して得た学び

こんにちは。ピクトリンク事業部でQAやDevOpsに思いを馳せている橋本です。
以前、リグレッションテストを整備した記事を書きました。

tech.furyu.jp

そちらの記事でも軽く言及していたとおり、私たちはMagicPodでリグレッションテストの自動化に取り組んできました。
今回はリグレッションテスト自動化にまつわる内容や工夫、課題についてお話したいと思います。

経緯のおさらい

  • リグレッションテストで手戻りを減らせた!
  • 機能が多くなればなるほど、リグレッションテストにかかる時間が増える
    • テストケース増大による工数増で、手作業がどんどんつらくなっていく
    • 急いでいるなどの理由から、テストケースを簡略化してしまう
    • 不具合を見落とすリスクも増える
  • 何度も同じテストを実行する性質上、自動化の恩恵が大きい

よろしい、ならば自動化だ

対象となるアプリ

今回、私たちがリグレッションテストの自動化に取り組んだアプリは「PICTLINK Photos」といいます。

PICTLINK photos

PICTLINK photos

  • FURYU Corporation
  • 写真/ビデオ
  • 無料
apps.apple.com

「PICTLINK Photos」はフリュー株式会社の提供する「ピクトリンク」有料会員向けのサービスです。
簡単操作、面倒臭い操作不要でフォトストレージ機能をご利用いただけます。

自動テストのざっくりした処理の流れ

以下のような流れで自動テストは実行されます。
(本記事執筆時点では運用に乗り切っていない/安定稼働していない部分を含みます)

ざっくりした処理の流れ
登場人物はGithub, MagicPod, slackだけです。シンプルですね。

Github Actionsではアプリのビルドを行い、magicpod-clients を利用してデプロイ&テスト実行します。
MagicPod上で動作する端末やOSバージョンは選択可能なので、OSバージョン固有の挙動等もテストできます。(あらかじめ端末設定が必要です)
画面サイズによって表示が変わるなどのありがちな確認も、様々な端末設定でテストできるので安心です。
同じテストケースを使って、iOSとAndroidをテストさせることもできます。それぞれ個別にテストを作ったりメンテナンスするのに比べると、単純計算で作業コストが半分になります。

遷移が少し深くてわかりにくい部分がありますが、『テスト一括実行』内の詳細設定に通知設定が用意されています。
メールやslackへの通知が公式サポートされているので、GUI上で通知先をポチポチ選ぶだけでテスト結果を送信できます。簡単&便利でハッピー。

support.magic-pod.com

テスト内容の一例

ユーザが主に行う操作を中心にテストケースを作成していきます。
「PICTLINK Photos」では、たとえば次のような項目のテストを作成しています。

  • データを初期化し、アプリをインストール、起動する
  • 初回起動の利用規約画面の確認
  • ログインが正しくできること
  • バックアップ(アップロード)されたデータの一覧が表示されていること
  • データをお気に入り登録できること/お気に入りを解除できること
  • データをゴミ箱に移動できること/ゴミ箱から復元できること/ゴミ箱から削除できること

テストが実際に動いている様子
画面がガビガビしているのはブログ掲載のためにgif動画にしたためで、実物はもっと綺麗に表示されます。

MagicPodは、裏ではAppiumが動いています。Appiumコンソールで実行ログの確認ができます。
成功するはずのテストがなぜか失敗するときなど、原因を特定するのに便利です。

自動化する上で困ったこと

手動のリグレッションテストをすべて自動テストとして構築完了! ヨシ!👈😤 第3部完!
…と、すんなりいけばよかったのですが、残念ながらそう簡単にはいきませんでした。
手動テストには手動テストのつらさがあるように、自動テストにも自動テストならではのつらさがあったのです。

動作が遅い

全般的に動作が遅く、画面遷移もモッサリしています。
これはMagicPod固有の欠点というよりも、バックエンドで動いているAppiumの欠点を引き継いでいる部分です。まぁAppiumに限った話でもなく、モバイルアプリの自動E2Eテストは軒並みモッサリ感との戦いみたいですが…。

テスト内容の項でご紹介したgif動画では軽快に動いているように見えますが、実はこの動画は 12倍速 なのです。
同様の動作が完了するまでに、実際には4分ほど掛かっています。

実行速度が遅いことによって、次のような困りがあります。

トライ&エラーに時間がかかる

テストケース作成は、作った自動テストを動かしてみて、期待結果通りになるかどうかを確認しながら進めることになります。
トライ&エラーに時間がかかるので、テストケース作成コストが重くなっていきます。

  1. まず、手動テストケースにあわせて自動テストケースを作り、実行してみる
    ゆっくりしていってね!
  2. 失敗した部分を調整し、再実行してみる
    ゆっくりしていってね!
  3. テストが通っていたはずの処理まで、タイミングによっては失敗する
    ゆっくりしていってね!

…oh(´・ω・`)

MagicPodではテストケース内の特定の処理だけを実行させることもできますが、ワンアクションごとの動作が遅いのは変わりません。

どうにか実行速度を上げられないかと試す中で、要素を識別するロケータを調整し、どれが速いのかも検証してみました。
その結果、xpathを利用すると実行時間は若干遅くなりますが、他のロケータを使っても劇的に速くなるほどの差はないことがわかりました。
差がないわけではないが、ボトルネックになってる部分はそこではない、って感じです。
そのため、ほんの少しの実行速度の差は気にしない方針としました。

nameやdata属性のような、常に一意であり、変更される可能性が少ないロケータを指定し、壊れにくさを優先してテストケースを作成しました。
遷移が大きく変わる場合はどうしようもないですが、ちょっとしたデザインの変更くらいならテストは絶対コケなく…は過言ですが、コケにくくはなります。

安定性を重視する方針を定めたことによって、結果的にトライ&エラーにかかる時間は削減できたように思います。

短時間表示される表示の確認が困難

たとえば『5秒間表示されたあと消える画面』のような、時間経過とともに表示が変わる要素のテストが困難です。
次の動作や確認に移るまでの間に5秒間が過ぎてしまい、表示が消えてしまうのです。

トライ&エラーを繰り返して最適な待ち時間を割り出したり、テスト用アプリ上での表示時間を延長するなどの工夫で、自動テストに組み込んだりもできなくはありません。
ですが、前者の対応だとネットワーク環境やデバイスの性能、アプリの改修によって待ち時間が変わり、テストが不安定化する懸念があります。
後者の対応では、本番アプリとテストアプリの挙動を分けたために、本番アプリでのみ発生する不具合を見逃してしまう可能性があります。

ただでさえ自動E2E(End to End)テストは壊れやすい(不安定になりやすい)ため、すべての機能、すべての表示を毎回確実に確認するのは現実的ではありませんでした。
最初は全部のテストケースを自動化しようとして、かなり頭を悩ませました。。。

苦手としているタイプの確認は、たとえばユニットテストで品質担保するなど、代替手段を模索したいです。
どうしてもE2Eテストで確認する必要がある場合、自動化を諦めるのもひとつの手でしょう。自動化はテストを楽にする手段であり、自動化するためにめちゃくちゃ苦しんでいては本末転倒になってしまいます。
安定して自動テストできる部分を自動化して、残った部分のみを手動確認することで、比較的楽にテストを終えられると思います。

冪等性(べきとうせい)をいかにして担保するか

冪等性(べきとうせい) は、同じ操作を何回繰り返しても、最初の1回目とまったく同じ結果が返る性質のことです。
リグレッションテストは同じテストを何度も繰り返して実行することによって品質を担保します。冪等性が保たれていないと、1回のテスト実行によってシステムやアカウントの状態が変化してしまい、次以降のテストに影響を与えてしまいます。

「PICTLINK Photos」はフォトストレージアプリであり、アップロードしたデータを削除する機能もあります。
こうした破壊的な操作もテストをする必要がありますが、データを削除するとデータが削除されてしまいます。(何を言ってるんだという感じですが)
データを削除されたまま、再度同じテストを行おうとすると削除対象となるデータがないので、今度はテストが失敗します。冪等性が保たれていないと、こういうことが起こります。

ユニットテストであれば、テスト実行時にダミーデータを登録して、そのデータに対して削除処理を行うなどで対処します。モックで置き換えたりもします。しかし、E2Eテストではそういった準備が難しい場合もあります。
テストアプリのデバッグ機能として、アカウントを任意の状態にする機能を追加するなど、冪等性を保つ工夫が必要です。

また、実行時間削減のために、アカウントの状態が変更される(破壊的な操作を含む)ようなテストケースを、他のテストケースと並行で実施したい場合もあるでしょう。
そういう場合には、テストケースごとに利用するアカウントを別に用意するのをおすすめします。状態が変わっているせいで、並行実施しているテストが失敗するのを防ぐためです。

変更がないのにコケる

いわゆる「何もしてないのに壊れた」が自動E2Eテストではわりと起こります。
その原因は、表示速度であったり、ネットワークの問題であったり、表示のランダム性(各種訴求のダイアログやキャンペーンのポップアップだったり)、たまたま飛んできたプッシュ通知、エミュレータ自体の不安定さであったりします。これら1つが噛み合わなかっただけでテストは失敗します。
逆説的に、様々な条件を潜り抜けたときのみ自動テストは成功する、と言い換えてもいいかもしれません。

そういった不安定なテストを、テスト界隈ではフレーキーテスト(flaky test)と呼ぶようです。
flakyは「薄片状の、フレーク状の」という意味の英単語で、パイ生地やクロワッサンなどの薄くてサックサクな食感を表す言葉です。テストでは「砕けやすい、壊れやすい」の意味になるみたいです。おいしそうなのに…。

flakyなクロワッサン

フレーキーテストをそのままにしていると、テストがコケるたびに失敗通知が飛んできます。
最初のうちは、失敗通知の原因を探ったり、改善できる部分を探したりもするでしょう。
しかし、失敗するのが「当たり前」「よくあること」になってしまうと、失敗通知が重要視されないようになっていきます。そうなると、本当にまずい問題が発生しているときにスルーしてしまう可能性が上がります。いわゆるオオカミ少年状態です。

毎回失敗するテストは、テストの内容を直せばいいのです。
でも、成功したり失敗したりがランダムなテストは無い方がマシな場合まであります。

フレーキーテストに対しては、ロケータの指定を見直して誤った箇所をタップしにくいようにしたり、リトライ戦略を練ったりします。良さげなロケータが設定されていない場合、テストのためにnameやdata属性の追加も視野に入れます。
それでもどうにもならない場合は手動でテストを実施するか、アプリの作りから見直すべきかもしれません。

リトライ戦略

たとえば3回に1回失敗するようなフレーキーテストがあったとします。失敗率33.33%です。
このテストがコケたとき、そのまま通知するのではなく、3回まではリトライするように設定します。
すると、3回連続で失敗したとき、すなわち 1/3 × 1/3 × 1/3 = 1/27、確率にして3.7%で通知が飛んでくるようになります。4回リトライにしたら1/81、1.23%です。
リトライさせる分だけテスト実行時間は延びてしまいますが、失敗通知の信頼性が下がるよりは随分マシでしょう。必要経費です。

今回は3回に1回失敗するテストを例にあげましたが、失敗率によってリトライ回数を調整して、フレーキーテストでもある程度は自動テストに任せられるようになります。

おわりに

E2Eテスト(リグレッションテスト含む)は実施コストが重く、自動テストの作成・保守も同様にコストがかかりますが、投資する価値は十分あると感じました。

アプリ・サービスの屋台骨(コア)、ないしユーザ不利益となり得るクリティカルな部分に絞って自動化するだけでも、品質の維持に寄与してくれるはずです。
不安定なテストは、失敗通知の信頼性が失墜する前に対処する必要があります。自動テストのカバー範囲をいたずらに広げ過ぎないのも、自動化戦略としては大いにアリだと思います。

関数のように共通パーツに切り出して安定性を上げたり、テスト作成にかかる時間を圧縮したりもできます。
自動テストを定期実行すると、アプリ・サービスの安定性や、失敗しやすいテストの統計情報も作れるようにもなります。失敗は成功の母、改善のとっかかり、のびしろです。

こうして品質まわりを整備することによって、プロジェクトチーム内での品質意識向上にも寄与できる…といいなぁ! と願いながら、より良い品質担保を模索していこうと思います。

参考:

  • 記事中のおいしそうなクロワッサン写真は pexels を利用しています
  • MagicPodのブランドロゴはこちらからお借りしました magicpod.com