本文へ移動

ホーム / App / 第5部 品質・配信・運用

第5部 品質・配信・運用・第12章

テスト・審査・配信

★★☆ 重要9トピック・面接 4問

アプリならではのリリースの仕組みと、品質を守る方法を押さえる。

12.1アプリのテスト

用語★★☆ 重要
ひとことで

ロジックは単体テスト、画面はスナップショットや UI テスト。端末の違いはクラウドの端末で確かめる。

アプリは出した後にすぐ直せないので、リリース前のテストの重みが Web より大きくなります。ただし、画面を動かすテストは遅くて壊れやすいので、粒度ごとにバランスよく持ちます。

テストの粒度

粒度道具の例向いている対象
単体テストiOS:XCTest・Swift Testing。Android:JUnit(必要なら Robolectric)ViewModel・計算・データの変換。速く、一番多く書く
結合テストテスト用の偽の API、メモリ上の DBRepository と DB・通信のつながり、マイグレーション
UI テストiOS:XCUITest。Android:Compose UI Test・Espressoログイン・購入など大事な流れだけ
スナップショットテスト画面の画像を前回と比べるライブラリ見た目の崩れ・ダークモード・文字の大きさ
下ほど多く、上ほど少なくUI テスト結合テスト単体テスト(一番多く)上ほど遅く、壊れやすい下ほど速く、安定している見た目の崩れはスナップショットテストで別に見張る
下ほど速く数を多く、上ほど遅いので大事な流れだけに絞る

テストしやすくするには、ViewModel が通信や DB を直接作らず、外から渡してもらう(DI)形にしておきます。時間や乱数も差し替えられるようにすると、結果が安定します。

面接ではこう答える

アプリは出した後にすぐ直せないので、リリース前のテストの重みが Web より大きくなります。ViewModel や計算は速い単体テストで一番多く書き、Repository と DB・通信のつながりやマイグレーションは結合テスト、ログインや購入など大事な流れだけを遅い UI テストで確かめ、見た目の崩れはスナップショットテストで見ます。テストしやすくするには、ViewModel が部品を外から受け取る形にし、時間や乱数も差し替えられるようにします。

12.2CI とビルドの自動化

用語★★☆ 重要
ひとことで

プルリクエストごとにビルド・静的解析・単体テストを流し、署名とストアへのアップロードも自動にする。

アプリのビルドは時間がかかり、署名などの手順も多いので、人の手でやると間違えます。CI で自動にしておくと、誰がやっても同じ結果になります。

プルリクエストからストアまでプルリクエストビルド・静的解析単体テストUI テスト(夜間)署名・アップロードiOS のビルドには macOS のランナーが必要。キャッシュで時間とお金を節約する
プルリクエストごとに速いものを流し、リリースのときに署名とアップロードまで自動で行う
  • プルリクエストごとに、ビルド・静的解析・単体テストを流します。UI テストは時間がかかるので、夜間やリリース前にまとめて流すこともあります。
  • iOS のビルドには macOS が必要です。GitHub Actions の macOS のランナーや Xcode Cloud を使います。macOS のランナーは料金が高めなので、キャッシュで時間を短くします。
  • Android はエミュレーターで UI テストを流せます(Gradle Managed Devices を使うと端末の設定もコードで書ける)。実機の幅は Firebase Test Lab などで補います。
  • 署名とストアへのアップロードは fastlane などで自動化し、署名の鍵は CI のシークレットに置きます。

UI テストがたまに落ちる(フレーキー)と、誰も結果を信じなくなります。固定の待ち時間を入れず、要素が出るのを待つ書き方にし、落ちやすいテストは直すか外します。

面接ではこう答える

アプリの CI では、プルリクエストごとにビルド、静的解析、単体テストを流し、時間のかかる UI テストは夜間やリリース前にまとめて流します。iOS のビルドには macOS が必要なので、GitHub Actions の macOS のランナーや Xcode Cloud を使い、キャッシュで時間を短くします。署名とストアへのアップロードは fastlane などで自動化し、鍵は CI のシークレットに置きます。たまに落ちるテストは直すか外し、結果を信じられる状態を保ちます。

本番に出す前に、本物の配信の仕組みで社内や一部の利用者に配って確かめます。Web の検証環境にあたるものです。

人数を広げながら確かめる社内のテストTestFlight 内部など外部のテストTestFlight 外部など本番(一部)段階的リリース本番(全員)外部のテストでは、配る前に簡単な審査が入ることがある(TestFlight の外部テストなど)
社内から外部へ、人数を広げながら確かめてから本番に出す
仕組みiOS:TestFlightAndroid:Google Play のテストトラック
社内向け内部テスト。App Store Connect のチームのメンバーに配る。審査なし内部テスト。少人数にすぐ配れる
外部向け外部テスト。メールや公開リンクで招待。最初のビルドは簡単な審査があるクローズドテスト(招待した人)・オープンテスト(誰でも参加)
期限・制限ビルドは一定の期間で期限が切れる新しく作った個人の開発者アカウントは、本番の前にクローズドテストを一定の人数・期間行う決まりがある

テスト配信の版は、本番と同じ署名・同じ設定(リリース版の最適化)で作るのが大事です。デバッグ版でしか確かめていないと、R8 で消えたクラスや、本番の API の設定の違いで、本番だけ落ちることがあります。

12.4ストアの審査

用語★★☆ 重要
ひとことで

ストアの審査を通って配信される。段階的リリースで少しずつ広げ、問題があれば止める。

App Store も Google Play も、アプリを出す前に審査があります。審査は多くの場合1〜2日以内に終わりますが、落ちると直して出し直しになります。

審査で落ちやすい点

  • 審査の人がログインできない:ログインが必要なアプリは、審査用のアカウントを申請時に渡します。
  • 落ちる・作りかけ:審査中にクラッシュする、空の画面や「準備中」のボタンが残っている。
  • アカウントの削除:アカウントを作れるアプリは、アプリの中から削除できるようにする必要があります(App Store)。
  • 課金:アプリの中で使うデジタルの商品は、原則としてストアのアプリ内課金を使う決まりがあります(国や条件による例外が増えているので、最新の規約を確かめる)。
  • 権限の説明:カメラや位置情報の許可を求める理由が書かれていない、使わない権限を求めている。
  • プライバシーの申告:集めているデータとストアに書いた申告が合っていない。

面接ではこう答える

App Store も Google Play も、アプリを出す前に審査があり、多くは1〜2日以内に終わりますが、落ちると直して出し直しになります。落ちやすいのは、審査の人がログインできない、審査中にクラッシュする、作りかけの画面が残っている、アカウントの削除ができない、デジタルの商品でストアの課金を使っていない、権限の理由が書かれていない、プライバシーの申告が実際と合っていない、といった点です。

ひとことで

新しい版を少しずつ広げ、指標が悪くなったら止める。止めても入れた人の版は戻らないので、Feature Flag と組み合わせる。

修正版は審査を通り、さらに利用者が更新するまで届かないので、新しい版は一度に全員に出さないのが基本です。

不具合を見つけても、修正版は審査を通り、さらに利用者が更新するまで届きません。そこで、一度に全員に出さない段階的リリースを使います。

7日かけて広げ、問題があれば止める(App Store)1%1日目2%2日目5%3日目10%4日目20%5日目50%6日目100%7日目問題があれば一時停止(合計30日まで)手動で更新する人には最初から届く
App Store の段階的リリース:自動アップデートの対象に7日かけて広げる
観点App Store:段階的リリースGoogle Play:段階的な公開
広げ方7日かけて自動で 1%→2%→5%→10%→20%→50%→100%公開する割合を自分で決めて少しずつ上げる
止め方一時停止できる(合計 30 日まで、回数の制限なし)公開を停止できる
注意対象は自動アップデートの人だけ。ストアで手動で更新する人には最初から届く停止しても、すでに入れた人の版は戻らない

最初の数日はクラッシュ率や問い合わせを見て、問題があれば止めます。致命的な問題には、修正版の審査を急いでもらう申請(App Store の迅速審査)もありますが、頼り切らず、アプリの中の機能をサーバー側から止められる Feature Flag を用意しておきます。

決めておくこと例
見る指標版ごとのクラッシュフリー率、ANR 率、主要な API のエラー率、問い合わせの数
止める基準前の版よりクラッシュフリー率が明らかに下がったら止める
止めた後原因を調べ、Feature Flag で機能を止めるか、修正版を出す

面接ではこう答える

段階的リリースは、新しい版を一度に全員に出さず、少しずつ広げる方法です。App Store は自動アップデートの人に7日かけて広げ、一時停止もでき、Google Play は公開する割合を自分で決めて上げていきます。最初の数日はクラッシュフリー率や問い合わせを見て、決めておいた基準より悪ければ止めます。ただし止めても、すでに入れた人の版は戻らないので、サーバー側から機能を止められる Feature Flag を組み合わせます。

ひとことで

機能の公開をサーバーから切り替え、致命的な問題のある古いバージョンには更新を促す。

Feature Flag は、機能を使うかどうかをアプリの更新なしにサーバー側から切り替える仕組みです。新しい機能を一部の人だけに出す、問題があったらすぐ止める(キルスイッチ)、A/B テストをする、といった使い方をします。

Remote Config など

Firebase Remote Config や、Feature Flag 専用のサービス、自社の設定 API などで配ります。使うときの注意は次のとおりです。

  • アプリの中に既定値を持つ:設定を取れなかった(初回起動・オフライン)ときにも正しく動くように。
  • 取りにいく間隔:毎回取りにいくと通信が増え、制限にかかることもある。キャッシュの時間を決め、「次の起動から反映」でよいものが多い。
  • 画面の途中で切り替えない:使っている最中に機能が消えると混乱するので、起動時や画面を開くときに読む。
  • 片付ける:全員に出し終わったフラグはコードから消す。残すと分岐が増え続ける。

面接ではこう答える

Feature Flag は、機能を使うかどうかをアプリの更新なしにサーバー側から切り替える仕組みで、一部の人だけに出す、問題があればすぐ止める、A/B テストをする、といった使い方をします。Remote Config などで配りますが、取れなかったときのためにアプリの中に既定値を持ち、取りにいく間隔を決め、使っている最中には切り替えず、全員に出し終わったフラグはコードから消します。

ひとことで

起動時に「動かせる最低の版」と「勧める版」を取り、古ければ更新を案内する。強制は最後の手段。

セキュリティの問題や API の大きな変更で、古い版をどうしても動かせないときの最後の手段が強制アップデートです。

古い版を動かせなくなったときに備えて、「推奨する版」と「最低の版」をサーバー側に持ちます。

設定アプリの動き
min_supported_version今の版がこれより古ければ、ストアへの案内だけを出して先に進めない(強制アップデート)
recommended_versionこれより古ければ「新しい版があります」と閉じられる案内を出す
設定が取れなくても止めない。比べるのは数字ごと起動設定を取得取れなければ既定値版 < 最低の版?強制アップデートストアへ案内だけ版 < 推奨の版?閉じられる案内閉じれば使えるそのまま使うはいいいえはいいいえ1.10.0 は 1.9.0 より新しい(文字列で比べない)。iOS と Android で別々に持つ
起動時に設定を取り、最低の版・推奨の版と比べて案内を出し分ける

版の比べ方は、文字列ではなく数字ごとに比べます("1.10.0" は "1.9.0" より新しい)。OS ごとに版の番号がずれることが多いので、iOS と Android で別々に持ちます。また、最低の版を上げるときは、その版で直したい問題が本当にあるかを確かめ、上げる前に何割の人が対象になるかを見ます。

API との付き合い方(強制アップデート)

Android では Google Play の In-App Updates を使うと、アプリの中で更新を進められます。最低の版を取れなかったとき(オフライン)に止めない作りも大事です。

面接ではこう答える

強制アップデートは、起動時にサーバーや Remote Config から動かせる最低の版と勧める版を取り、今の版が最低の版より古ければストアへの案内だけを出して先に進めない仕組みです。勧める版より古いだけなら、閉じられる案内にとどめます。版は文字列ではなく数字ごとに比べ、OS ごとに別々に持ち、取れなかったときに止めないようにします。上げる前には、何割の人が対象になるかを確かめます。

ひとことで

見せる版(1.5.0)とストアが比べる番号(versionCode・Build)を分けて持ち、決まった周期のリリーストレインで出す。

アプリには、利用者に見せる版と、ストアが新旧を見分けるための番号の2つがあります。

番号iOSAndroid
見せる版(例:1.5.0)CFBundleShortVersionString(Marketing Version)versionName
新旧を見分ける番号CFBundleVersion(Build)。同じ版の中で増やすversionCode。整数で、出すたびに必ず増やす

リリースの周期は、リリーストレイン(毎週・2週ごとなど決まった日に出す)が多く使われます。準備できた機能だけを乗せ、間に合わない機能は Feature Flag で隠したまま次に回すので、「あの機能を待って全体が遅れる」ことがなくなります。リリースのブランチを切ってコードを凍結し、テストと審査の間に見つかった不具合はそのブランチで直します。

決まった周期で「列車」を出す(例:2週間ごと)版 1.4.0コード凍結 → テスト → 審査版 1.5.0コード凍結 → テスト → 審査版 1.6.0コード凍結 → テスト → 審査間に合わない機能は次の列車へ(Feature Flag で隠して先にマージしておく)緊急の修正だけは、リリースのブランチから 1.5.1 のような版で別に出す
決まった周期で、そのとき準備できた機能だけを乗せて出す

面接ではこう答える

アプリの版には、利用者に見せる版と、ストアが新旧を見分けるための番号があり、Android の versionCode は出すたびに必ず増やす整数です。リリースはリリーストレインのように決まった周期で出すことが多く、その日に準備できた機能だけを乗せ、間に合わない機能は Feature Flag で隠して次に回します。こうすると予定が立てやすく、1つの機能の遅れで全体が止まりません。

ひとことで

アプリエンジニアは端末からログ・イベントを確実に送る実装を担当する。何を測るかは PdM・アナリスト、取り込みはデータエンジニアと決める。

アプリでは、ログやイベントを端末から送る実装をアプリエンジニアが受け持ちます。端末は電波が切れたり、アプリがすぐ終了されたりするので、Web より「確実に送る」工夫が要ります。

ログアプリエンジニアがやること一緒に決める相手
行動ログ(イベント)分析 SDK(Firebase Analytics など)や自社の送信の仕組みを入れ、トラッキングプランどおりに送る。オフラインの間は端末にためてまとめて送るPdM・アナリスト(何を測るか)、データエンジニア(形・取り込み)
クラッシュ・ANRCrashlytics などを入れ、版・端末と紐付ける。シンボル(dSYM・マッピングファイル)を上げるQA・SRE
アプリのログ(デバッグ用)本番では出しすぎない。個人情報・トークンを出さないセキュリティ
性能の数字起動時間・フレームの遅れなどを送る(MetricKit・Firebase Performance など)SRE・PdM
同意と追跡iOS の App Tracking Transparency(ATT)、各国の同意の画面、プライバシーの申告(App Store・Google Play)と実装を合わせるPdM・法務

アプリは一度配ると古い版が長く残るので、イベントの名前や項目を変えると、古い版と新しい版で数字が混ざります。変えるときは版を区別できる項目(アプリの版)を必ず送り、データ側と集計の仕方を決めてから出します。分担の全体は Data のログは誰が設計・実装するかにまとめています。

面接ではこう答える

アプリエンジニアは、行動ログやクラッシュを端末から送る実装を担当します。何を測るかは PdM やアナリストが決め、イベントの形と取り込みはデータエンジニアと合わせます。端末はオフラインやすぐの終了があるので、イベントは端末にためてまとめて送り、アプリの版も一緒に送ります。古い版が長く残るので、名前や項目を変えるときはデータ側と集計の仕方を決めてから出します。

この章のまとめ

🖨 まとめシート(印刷・PDF)
  • App Store / Google Play の審査と段階的リリース
  • 古いバージョンが残る前提での機能の出し方(Feature Flag・強制アップデート)
  • アプリのテスト:ロジックは単体テスト、画面はスナップショットや UI テスト。端末の違いはクラウドの端末で確かめる。
  • CI とビルドの自動化:プルリクエストごとにビルド・静的解析・単体テストを流し、署名とストアへのアップロードも自動にする。
  • ストアの審査:ストアの審査を通って配信される。段階的リリースで少しずつ広げ、問題があれば止める。
  • 段階的リリースの運用:新しい版を少しずつ広げ、指標が悪くなったら止める。止めても入れた人の版は戻らないので、Feature Flag と組み合わせる。
  • Feature Flag と Remote Config:機能の公開をサーバーから切り替え、致命的な問題のある古いバージョンには更新を促す。
  • 最低バージョンと強制アップデート:起動時に「動かせる最低の版」と「勧める版」を取り、古ければ更新を案内する。強制は最後の手段。
  • 版の番号とリリースの周期:見せる版(1.5.0)とストアが比べる番号(versionCode・Build)を分けて持ち、決まった周期のリリーストレインで出す。
  • ログとイベントの担当(行動ログ・クラッシュ・同意):アプリエンジニアは端末からログ・イベントを確実に送る実装を担当する。何を測るかは PdM・アナリスト、取り込みはデータエンジニアと決める。

この章のクイズ

面接ではこう聞かれる

まず答えを見ずに声に出して答え、そのあと開いて比べる。

Q1リリース後に致命的な不具合が見つかったらどうしますか?

模範回答

まず被害を広げないことです。段階的リリース中なら、App Store なら一時停止、Google Play なら公開を止めて、新しく入れる人を増やさないようにします。その機能を Feature Flag で出しているなら、サーバー側で無効にして、すでに入れた人の手元でも問題を止めます。サーバー側の変更で避けられる不具合なら、API の挙動を版ごとに変えて回避することもあります。

同時に、Crashlytics などで影響のある版・機種・人数を確かめ、修正版を作ってテストし、審査に出します。致命的なら迅速審査を申請します。修正版を出したら、壊れた版の利用者を確実に移すために、必要なら最低バージョンを上げて強制アップデートをかけます。落ち着いたら、なぜテストや段階的リリースで気づけなかったかを振り返り、テストや監視を足します。

深掘りされたら

  • Feature Flag で止められない不具合だったらどうしますか?
  • 段階的リリースの割合を上げる判断の基準は?

見られているポイント段階的リリースの停止・Feature Flag・修正版の審査

Q2段階的リリースはどう運用しますか?

模範回答

新しい版は全員に一度に出さず、少ない割合から広げます。App Store は7日かけて自動で 1% から 100% まで広がり、Google Play は自分で割合を決めて上げます。広げる前に、クラッシュフリー率・ANR 率・問い合わせ・主要な指標を見て、前の版より悪くなっていないかを確かめます。

悪くなったら止めます。App Store は一時停止、Google Play は公開の停止ができます。ただし、すでに入れた人の版は戻らず、App Store では手動で更新する人には最初から届くので、Feature Flag で機能を切れるようにしておくことが欠かせません。

深掘りされたら

  • 割合を上げる基準はどう決めますか?
  • 止めた後、修正版はどう出しますか?

見られているポイント見る指標と、止めても戻らない点の理解

Q3アプリのテストは、どの粒度で何を書きますか?

模範回答

一番多く書くのは単体テストで、ViewModel や計算、データの変換を速く確かめます。そのために ViewModel が通信や DB を直接作らず、外から渡してもらう形にしておきます。次に、Repository と DB・偽の API をつないだ結合テストや、DB のマイグレーションのテストを書きます。

画面を動かす UI テストは遅く壊れやすいので、ログインや購入など大事な流れだけに絞ります。プルリクエストごとに単体テストを CI で流し、UI テストは夜間やリリース前にまとめて流します。たまに落ちるテストは放置せず、直すか外します。

深掘りされたら

  • iOS の CI で困ることは?
  • スナップショットテストは使いますか?

見られているポイント粒度ごとの役割と、CI での回し方

Q4Feature Flag を運用するときに気をつけることは?

模範回答

まず、設定を取れなかったときのためにアプリの中に既定値を持ち、初回起動やオフラインでも正しく動くようにします。取りにいく間隔とキャッシュの時間を決め、使っている最中に機能が消えないよう、起動時や画面を開くときに読みます。

次に、フラグごとに持ち主と消す予定を決めます。全員に出し終わったフラグを残すと分岐が増え続け、組み合わせのテストもできなくなるからです。キルスイッチのように残すものは、本当に止められるかを定期的に確かめます。

深掘りされたら

  • フラグの組み合わせが増えたとき、テストはどうしますか?

見られているポイント既定値・切り替えのタイミング・片付け