Upgrade to Pro — share decks privately, control downloads, hide ads and more …

EspressoではじめるAndroid UIテスト / Android UI Testing Starting with Espresso

EspressoではじめるAndroid UIテスト / Android UI Testing Starting with Espresso

DroidKaigi 2020で開催予定だったハンズオンセッション「EspressoではじめるAndroid UIテスト」のスライドパートです。

後続のハンズオンパートはDeNA Codelabsの「Espresso APIを使いこなしてUIテストを書いてみよう」を参照してください。

TOYAMA Sumio

February 20, 2020
Tweet

More Decks by TOYAMA Sumio

Other Decks in Programming

Transcript

  1. ⓒ 2020 DeNA Co., Ltd. 2020/02/20 システム本部 品質統括部 品質管理部 SWETグループ

    DeNA Co., Ltd. 1 DroidKaigi 2020ハンズオン
 EspressoではじめるAndroid UIテスト
  2. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 講師紹介 2 • 氏名: 田熊 希羽 (Nozomi Takuma) @fgfgtkm (Twitter) / @tkmnzm (GitHub) • 所属: DeNA SWETグループ (Software Engineer in Test) • Androidアプリ開発のテスタビリティ改善・テスト自動 化支援 • 本日13:00~「自動生成でさくさく実装するユニットテス ト」というタイトルで発表しました
  3. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 講師紹介 • 氏名: 外山 純生 (TOYAMA Sumio) @sumio_tym (Twitter) / @sumio (GitHub) • 所属: DeNA SWETグループ (Software Engineer in Test) • 業務内容: − Androidアプリ開発 テスト自動化支援 • その他: − 「Androidテスト全書」執筆 − 明日17:00~「Robolectricの限界を理解してUIテストを高 速に実行しよう」@Pickersで登壇します! 3
  4. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. この資料について 本資料は、関係者の許諾の元で 書籍「Androidテスト全書」の「4章 UIテスト(概要編)」 の内容を一部流用して作成しています。 「Androidテスト全書」にはここで紹介する内容のほか、 役立つ情報が満載です。 気になる方は読んでみてください! https://peaks.cc/android_testing 4
  5. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 本ハンズオンの目標 • UIテストの特徴を理解する • UIテストを書き始める足がかりとして 次のAPIを理解する (DeNA Codelabs) − Activity・Fragmentを起動するAPI − Espressoの基本的なAPI − RecyclerView操作のAPI (自習) − 画面更新を待ち合わせるAPI (自習) 5
  6. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 本資料の構成 本日のハンズオンでは一部割愛します 1. UIテストの自動化を始める前に 2. テストツール選択のポイント 3. 長くテストコードを利用し続けるには 4. Espresso APIを使いこなしてUIテストを書いて みよう(DeNA Codelabs) 6
  7. ⓒ 2020 DeNA Co., Ltd. 1. UIテストの自動化を始める前に 7 1. UIテストの特徴

    2. 目的を整理する 3. 自動化する範囲を決める 4. テストツールを選択する 5. スケジュール・予算・体制を決める
  8. ⓒ 2020 DeNA Co., Ltd. 1. UIテストの自動化を始める前に 8 1. UIテストの特徴

    2. 目的を整理する 3. 自動化する範囲を決める 4. テストツールを選択する 5. スケジュール・予算・体制を決める
  9. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-1. UIテストの特徴① ユニットテスト vs UIテスト 9 This image is reproduced from work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
  10. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-1. UIテストの特徴① ユニットテスト vs UIテスト 10 This image is reproduced from work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License. UIテストはユニットテストに比べて・・ • より実環境に近い(Fidelityが高い) • 実行時間が長い • メンテナンスコストが高い • デバッグコストが高い
  11. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-1. UIテストの特徴② (参考) UIテストの分類 11 忠実度 実行時間 分類 高 遅い E2E Test 中 普通 Instrumented Test 低 速い Local Test (Robolectric)
  12. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-1. UIテストの特徴③ • 画面デザインが変更されると、テストコードも修 正しなければならない • UIに起因する理由でテストが安定しない − UI操作→画面変化までの時間がまちまち − 操作を中断するダイアログが突然表示されるかも − ログイン画面 − パーミッションダイアログ 12 無計画に自動化すると 運用コストの増加に耐えられなくなることに!
  13. ⓒ 2020 DeNA Co., Ltd. 1. UIテストの自動化を始める前に 14 1. UIテストの特徴

    2. 目的を整理する 3. 自動化する範囲を決める 4. テストツールを選択する 5. スケジュール・予算・体制を決める
  14. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-2. 目的を整理する① テスト自動化の利害関係者は意外とたくさん • プロダクトオーナー • QAチーム • 開発チーム • etc. 15 UIテスト自動化への期待は人それぞれ!
  15. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-2. 目的を整理する② • テスト担当者への精神的負担の軽減 「退屈な(MPを消費する)テストから解放されたい」 • 開発コストの削減 「リリースまでのリードタイム短縮したい」 • 既存機能のバグを予防 「思わぬバグを入れてしまう不安から解放されたい」 16 UIテスト自動化への期待を関係者で議論する 「検証コストの削減」以外にも・・・
  16. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-2. 目的を整理する③ 条件を追加すると達成しやすい • 検証コスト削減 + 精神的負担の軽減 • 検証コスト削減 + 既存機能のバグを予防 17 UIテスト自動化で一番難しいこと • 自動化にかかる投資を 検証コストの減少だけで回収すること 意思決定者と合意しておくのを忘れない!
  17. ⓒ 2020 DeNA Co., Ltd. 1. UIテストの自動化を始める前に 18 1. UIテストの特徴

    2. 目的を整理する 3. 自動化する範囲を決める 4. テストツールを選択する 5. スケジュール・予算・体制を決める
  18. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-3. 自動化する範囲を決める① • 自動化できるものは全て自動化する • 目的から逸れた箇所を自動化して満足してし まう 19 自動化の範囲をむやみに広げると メンテナンスコストが上がってしまう! UIテスト自動化のアンチパターン
  19. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-3. 自動化する範囲を決める② 1. 手動・自動関係なく、どのような切り口でテスト をするか整理する 2. 目的を達成できる自動化候補を選ぶ − 検証コスト削減が目的なら・・・ 何度も行うテスト・時間がかかるテスト − QAの精神的負担軽減が目的なら・・・ 担当者が辛いと思っているテスト 20
  20. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-3. 自動化する範囲を決める③ 3. 自動化候補に優先度をつける − 自動化が容易なものを優先する 採用するテストツールによっても変化する − 変更可能性が少ない画面を優先する − 自動操作する画面数が少なくなるようにする 自動操作するUIコンポーネント数が増えると辛い (テスト書く工数が増える) 4. 予算内に収まるように自動化する範囲を決める 21
  21. ⓒ 2020 DeNA Co., Ltd. 1. UIテストの自動化を始める前に 22 1. UIテストの特徴

    2. 目的を整理する 3. 自動化する範囲を決める 4. テストツールを選択する 5. スケジュール・予算・体制を決める
  22. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-4. テストツール(*)を選択する① 次の観点でテストツールを選ぶ • 自動化したい内容が簡単に実現できるか − トラブル無く、安定してテスト実行できるか • 低コストで学習できるか − 担当者にとって書きやすいか − 近くに質問できる人がいるか • 開発コミュニティは活発か − 今後も開発が続きそうか? − 情報が充実しているか? 23 (*) ここではUIテスト自動化を実現するツールのことを指す
  23. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-4. テストツールを選択する② 自動化が不可能・困難なものは どのツールを選択しても無理なことに注意 • 端末に対する物理的な操作 − 端末を傾ける、振る、など • Viewと無関係な場所の操作 − 指で文字を描く、など 24
  24. ⓒ 2020 DeNA Co., Ltd. 1. UIテストの自動化を始める前に 25 1. UIテストの特徴

    2. 目的を整理する 3. 自動化する範囲を決める 4. テストツールを選択する 5. スケジュール・予算・体制を決める
  25. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5. スケジュール・予算・体制を決める それぞれポイントに絞って紹介します 1. スケジュール − とにかくスモールスタートで始める − リリース優先度を考慮したスケジュールにする 2. 予算 − ROIを意識する − 目標を決めておく 3. 体制 − テスト自動化担当者を確保する 26
  26. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-1. スケジュール検討のポイント① とにかくスモールスタートで始めよう • UIテストでは運用フェーズで直面する課題が多い • 運用期間をできるだけ長く確保するのが大事 ❌ 全部作ってからCIで運用を開始! 一つだけ作ったらCIで運用を開始! 28
  27. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-1. スケジュール検討のポイント② リリース優先度を考慮したスケジュールにしよう (新機能のテストを自動化する場合) • リリース優先度が高いテストから自動化する • 自動化が間に合わなければQAに検証依頼 • 自動テストの結果レポートを元にリリース内容を調整 29
  28. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-2. 予算検討のポイント① ROI (Return On Investment)を意識する • 自動化にかけたコストに対する利益の割合 • 1を超えれば投資を回収できたことになる • 自動テストを繰り返すとROIが増えることが多い 31 テスト自動化の利益 テスト自動化の初期コスト + テスト自動化後の運用コスト ROI =
  29. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-2. 予算検討のポイント② ROIの計算: 自動化にかかるコスト (: 時間の経過ともに増えていくもの) • 初期コスト − テストツールの学習コスト − テストを書くコスト • 運用コスト − テスト結果レポートの解析・原因切り分けコスト − プロダクト仕様変更にともなうテスト修正コスト 32
  30. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-2. 予算検討のポイント③ ROIの計算: テスト自動化の利益 以下の項目の和を計算する 33 ➕/ ➖ 項目 ➕ 手動のままテストを続けたときの運用コスト ➖ 自動化にかかる初期コスト ➖ テストを自動化した後の運用コスト
  31. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-2. 予算検討のポイント④ ROIの定義をもう一度 34 テスト自動化の利益 テスト自動化の初期コスト + テスト自動化後の運用コスト ROI = • 通常は テスト自動化の利益>テスト自動化後の運用コスト • 運用が長くなると(自動テストを繰り返すと) ROIが1を超えることが多い
  32. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-2. 予算検討のポイント⑤ 目標を決めておく • 改善策の検討や継続可否の判断のために必要 − ROIが1を超える(損益分岐点に達する)時期 (それまでに必要な自動テスト実施回数) − 検証コスト削減以外が目的の場合は・・・ どれだけコストをかけ続けられるか 35
  33. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-2. 予算検討のポイント⑥ 目標を決めたいけど見積もりが難しいとき • 初期開発コストは頑張って見積もる − 1ケースだけ作成し、その実績を元に見積もる − 過去の事例を参考にする − 経験者に聞く • 自動化の運用コストはざっくり決める (例: 2人時/週) − 後から精度を上げていけばOK 36
  34. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-2. 予算検討のポイント⑦ 目標の達成度を知るために 運用時に作業時間を記録すること! • ROIを計算するために必要 • 記録しないと、自動化が成功しているかどうか判断で きなくなるので重要! 37
  35. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 1-5-3. 体制検討のポイント テスト自動化担当者を確保する • 専任のテスト自動化担当者を設けるのは難しい − テストを自動化できる=ソフトウェアを開発できる − プロダクトの開発で忙しいことが多い • 兼任の場合は、以下を必ず確保する − 自動化に必要な学習・実装の時間 − 有識者に相談できるルート (片手間で自動化するのは無理) 39
  36. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. ここまでのまとめ • 関係者でUIテストの目的を決めましょう − 検証コスト削減以外の目的もあり • 目的を達成できるように以下を決めていく − 自動化する範囲 − 目的の範囲内で容易に実現できるものを優先 − 採用するテストツール − スケジュール・予算・体制 − スモールスタートで早く運用に入る − ROIを意識する − 自動化のための時間を確保できる体制にする 40
  37. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 2-1. EspressoとAppiumの概要① Espresso • AndroidX Testの一部として公式に提供 • 実機・エミュレータで動くInstrumented Test (Robolectricでも動くようになってきた) • APIレベル19以上であれば、 UI Automatorと併用して他アプリの操作も可能 43
  38. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 2-1. EspressoとAppiumの概要② Appium • 3rd party OSS(開発: JS Foundation) • リリース版apkをそのままテストできる (E2E Testに向いている) • Selenium WebDriverと同様なAPIで Androidアプリをテストする • クライアント/サーバアーキテクチャ • 内部はEspressoやUI Automatorを使って実現 44
  39. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 2-1. EspressoとAppiumの概要③ (参考)Appiumのアーキテクチャ 45 Appium クライアントラ イブラリ スクリプト Appium サーバ テスト対象 アプリ HTTP (Mobile JSON Wire Protocol) EspressoやUI Automator を使って操作 クライアントPC サーバーPC スマートフォン USB
  40. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 2-2. EspressoとAppiumの比較① Appiumも内部でEspressoを使うようになった (Appium Espresso WebDriver)ので、 できることの違いはほとんど無くなった (違いの例) • Espresso: RecyclerView専用API • Appium: OpenCVによる画像検索API 47
  41. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 2-2. EspressoとAppiumの比較② 48 Espresso Appium テスト実行速度 速い 遅い プログラミング言 語 Java・Kotlinのみ Ruby・JavaScript・Ja va・Kotlinなど テストランナーの 実行場所 Android端末内部(*) 通常のPC 環境構築 簡単 難しい 対応DeviceFarm 多い 少ない (*)Robolectricを使うことでlocal JVMでも実行可能
  42. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 2-2. EspressoとAppiumの比較③ UIテストは実行速度が命! 以下の事情がなければEspressoを選ぶと良い • 既にAppiumによる自動化ノウハウがある • テスト自動化担当者がAndroidエンジニアではない • Androidアプリ以外の操作を含むテストシナリオを自動 化したい 「PCブラウザで会員登録してから アプリでログインする」など 49
  43. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 2-2. EspressoとAppiumの比較④ (参考) Appiumを選択する場合 パフォーマンス向上策の参考記事 • Appium Pro #50 (Android向け) Special Capabilities for Speeding up Android Test Initialization https://appiumpro.com/editions/50 • Appium Pro #77 (iOS向け) Optimizing WebDriverAgent Startup Performance https://appiumpro.com/editions/77 50
  44. ⓒ 2020 DeNA Co., Ltd. 3. 長くテストコードを利用 し続けるには 51 1.

    Page Objectデザインパターンを適用する 2. CI環境でテストコードを動かし続ける 3. 検証結果をわかりやすく共有する 4. 不安定なテストを対策する 5. 実行時間を短縮させる 6. 開発・リリースフローに組み込む
  45. ⓒ 2020 DeNA Co., Ltd. 3. 長くテストコードを利用 し続けるには 52 1.

    Page Objectデザインパターンを適用する 2. CI環境でテストコードを動かし続ける 3. テスト結果をわかりやすく共有する 4. 不安定なテストを対策する 5. 実行時間を短縮させる 6. 開発・リリースフローに組み込む
  46. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 3-1. Page Objectデザインパターンを適用する • 画面を1つのオブジェクトとして定義するデザインパ ターン • テストコードを共通化する指針 • 対象アプリのUI が変更されたときのテストコード修正 コストを小さくすることを目的としている DeNA Codelabs ( https://dena.github.io/codelabs/ ): 「Espressoの知識ゼロでも書ける!Android UIテストはじめの 一歩」 53
  47. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. テストコードの共通化: テスト対象 54 ユーザーID パスワード ログイン invalid_user ********** ログイン ログインできません ログイン失敗 ようこそ! ログイン成功
  48. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 55 @Test fun login_success() { //ユーザー名とパスワードを入力してログイン onView(withHint("ユーザーID")).perform(replaceText("valid_user")) onView(withHint("パスワード")).perform(replaceText("valid_password")) onView(withText("ログイン")).perform(click()) //ログインが成功して「ようこそ」と表示されている onView(withId(R.id.welcomeMessage)).check(matches(withText("ようこそ! "))) } @Test fun login_error() { // 不正なユーザー名とパスワードを入力してログインボタンをクリック onView(withHint("ユーザーID")).perform(replaceText("invalid_user")) onView(withHint("パスワード")).perform(replaceText("invalid_password")) onView(withText("ログイン")).perform(click()) // ログインエラーのメッセージが表示されている onView(withId(R.id.errorMessage)).check(matches(withText("ログインできません"))) } テストコードの共通化: 共通化前
  49. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. テストコードの共通化: 共通化前 56 @Test fun login_success() { //ユーザー名とパスワードを入力してログイン onView(withHint("ユーザーID")).perform(replaceText("valid_user")) onView(withHint("パスワード")).perform(replaceText("valid_password")) onView(withText("ログイン")).perform(click()) //ログインが成功して「ようこそ」と表示されている onView(withId(R.id.welcomeMessage)).check(matches(withText("ようこそ! "))) } @Test fun login_error() { // 不正なユーザー名とパスワードを入力してログインボタンをクリック onView(withHint("ユーザーID")).perform(replaceText("invalid_user")) onView(withHint("パスワード")).perform(replaceText("invalid_password")) onView(withText("ログイン")).perform(click()) // ログインエラーのメッセージが表示されている onView(withId(R.id.errorMessage)).check(matches(withText("ログインできません"))) } 重複が多い
  50. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. テストコードの共通化: 共通化前 57 @Test fun login_success() { //ユーザー名とパスワードを入力してログイン onView(withHint("ユーザーID")).perform(replaceText("valid_user")) onView(withHint("パスワード")).perform(replaceText("valid_password")) onView(withText("ログイン")).perform(click()) //ログインが成功して「ようこそ」と表示されている onView(withId(R.id.welcomeMessage)).check(matches(withText("ようこそ! "))) } @Test fun login_error() { // 不正なユーザー名とパスワードを入力してログインボタンをクリック onView(withHint("ユーザーID")).perform(replaceText("invalid_user")) onView(withHint("パスワード")).perform(replaceText("invalid_password")) onView(withText("ログイン")).perform(click()) // ログインエラーのメッセージが表示されている onView(withId(R.id.errorMessage)).check(matches(withText("ログインできません"))) } ボタン名が「ログインする」に 変更されたとき、2箇所修正が必要
  51. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. テストコードの共通化: ログイン操作を共通化 58 @Test fun login_success() { onView(withHint("ユーザーID")).perform(replaceText("valid_user")) onView(withHint("パスワード")).perform(replaceText("valid_password")) onView(withText("ログイン")).perform(click()) onView(withId(R.id.welcomeMessage)).check(matches(withText("ようこそ! "))) } fun login(id: String, password: String) { onView(withHint("ユーザーID")).perform(replaceText(id)) onView(withHint("パスワード")).perform(replaceText(password)) onView(withText("ログイン")).perform(click()) } ログイン操作を 抽出
  52. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 59 @Test fun login_success() { login("valid_user", "valid_password") onView(withId(R.id.welcomeMessage)).check(matches(withText("ようこそ! "))) } @Test fun login_error() { login("invalid_user", "invalid_password") onView(withId(R.id.errorMessage)).check(matches(withText("ログインできません"))) } private fun login(id: String, password: String) { onView(withHint("ユーザーID")).perform(replaceText(id)) onView(withHint("パスワード")).perform(replaceText(password)) onView(withText("ログイン")).perform(click()) } テストコードの共通化: 共通化後
  53. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 60 @Test fun login_success() { login("valid_user", "valid_password") onView(withId(R.id.welcomeMessage)).check(matches(withText("ようこそ! "))) } @Test fun login_error() { login("invalid_user", "invalid_password") onView(withId(R.id.errorMessage)).check(matches(withText("ログインできません"))) } private fun login(id: String, password: String) { onView(withHint("ユーザーID")).perform(replaceText(id)) onView(withHint("パスワード")).perform(replaceText(password)) onView(withText("ログイン")).perform(click()) } テストコードの共通化: 共通化後 ボタン名が「ログインする」に 変更されたときの修正はここのみ
  54. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. Page Objectデザインパターンの考え方 1. テスト対象の画面ごとにクラス(Page クラス)を定義する 2. Page クラスにその画面が提供するサービスをメソッドとして定 義する a. アクションを実行するメソッド・ページ情報を取得するメソッドなど 3. アクションを実行するメソッドは戻り値として、 遷移先の画面に対応するPageオブジェクトを返す 4. テストコードではPageクラスのメソッドのみを使う 61
  55. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. Page Objectのサービス 62 • 画面が提供するサービスとは何か?に着目し、メソッ ドとして定義する − 画面の詳細や構造はPage Objectの中に隠蔽 • 例: ログイン画面 fun login(id, password) LoginPage fun inputID(id) fun inputPass(password) fun clickLogin() LoginPage
  56. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. ログイン画面のテストにあてはめる 1. loginメソッドをLoginPageクラスに移動する 2. 画面に表示されるメッセージを検証するコードもそれぞれの Pageクラスに移動する a. ログイン成功時のメッセージ検証: MainPage b. ログイン失敗時のメッセージ検証: LoginPage 3. 各テストメソッドではPageクラスに定義されたメソッドのみ利用 する 63
  57. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. ログイン画面のテストにあてはめる 64 test_login_success() test_login_failure() LoginTest login_success(id, password): MainPage login_failure(id, password): LoginPage assertErrorMessage(message): LoginPage LoginPage assertWelcomeMessage(message): MainPage MainPage 利用する
  58. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. ログイン画面のテストにあてはめる 65 test_login_success() test_login_failure() LoginTest login_success(id, password): MainPage login_failure(id, password): LoginPage assertErrorMessage(message): LoginPage LoginPage assertWelcomeMessage(message): MainPage MainPage 利用する 画面ごとにPageクラスを 定義する
  59. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. test_login_success() test_login_failure() LoginTest ログイン画面のテストにあてはめる 66 login_success(id, password): MainPage login_failure(id, password): LoginPage assertErrorMessage(message): LoginPage LoginPage assertWelcomeMessage(message): MainPage MainPage 利用する 戻り値は遷移先の画面の Pageクラスにする
  60. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. ログイン画面のテストにあてはめる 67 test_login_success() test_login_failure() LoginTest login_success(id, password): MainPage login_failure(id, password): LoginPage assertErrorMessage(message): LoginPage LoginPage assertWelcomeMessage(message): MainPage MainPage 利用する テストコードはPageクラスの メソッドのみ用いて実装する
  61. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 68 class LoginPage { fun loginSuccess(id: String, password: String): MainPage { login(id, password) return MainPage() } fun loginFailure(id: String, password: String): LoginPage { login(id, password) return this } private fun login(id: String, password: String) { onView(withHint("ユーザーID")).perform(replaceText(id)) onView(withHint("パスワード")).perform(replaceText(password)) onView(withText("ログイン")).perform(click()) } .. ログインページクラスの実装: アクション
  62. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. ログインページクラスの実装: アクション 69 class LoginPage { fun loginSuccess(id: String, password: String): MainPage{ login(id, password) return MainPage() } fun loginFailure(id: String, password: String): LoginPage { login(id, password) return this } fun login(id: String, password: String) { onView(withHint("ユーザーID")).perform(replaceText(id)) onView(withHint("パスワード")).perform(replaceText(password)) onView(withText("ログイン")).perform(click()) } ..  画面のアクションをメソッドとして実装
  63. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. class LoginPage { fun loginSuccess(id: String, password: String): MainPage { login(id, password) return MainPage() } fun loginFailure(id: String, password: String): LoginPage { login(id, password) return this } .. 遷移をせず画面にとどまる場合は thisを返す ログインページクラスの実装: アクション 70 戻り値はアクションをした後の 遷移先画面のPageクラス
  64. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. ログインページクラスの実装: アクション 71 class LoginPage { fun loginSuccess(id: String, password: String): MainPage{ login(id, password) return MainPage() } fun loginFailure(id: String, password: String): LoginPage { login(id, password) return this } private fun login(id: String, password: String) { onView(withHint("ユーザーID")).perform(replaceText(id)) onView(withHint("パスワード")).perform(replaceText(password)) onView(withText("ログイン")).perform(click()) } ..  各アクションで使われるログイン処理の 共通メソッド
  65. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 72 class LoginPage { .. fun assertErrorMessage(message: String): LoginPage { onView(withId(R.id.errorMessage)) .check(matches(withText(message))) return this } .. } ログインページクラスの実装: アサーション ログイン失敗時の エラーメッセージ検証メソッド
  66. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. Pageクラスを用いたログインのテストコード 73 class LoginTest { @Test fun login_error() { LoginPage() .loginFailure("invalid_user", "invalid_password") .assertErrorMessage("ログインできません") } .. } テストコードはPageクラスの メソッドのみ用いて実装する
  67. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. Page Objectを実装するときのポイント 74 • Pageクラスの外には画面UIの詳細を露出させない(レ イアウト構造など) • 画面が提供するサービスをすべてPageクラスに実装 する必要はない • 同じアクションでも遷移先が異なる場合は それぞれ別のメソッドにする
  68. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. Page Object参考リンク 75 • SeleniumのPage Object解説 − https://github.com/SeleniumHQ/selenium/wiki/PageObjects − assertをテストケースで実装するなど、 Espressoを使っている場合は実現できない原則もある • Martinfowler.com − https://martinfowler.com/bliki/PageObject.html
  69. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. Page Objectデザインパターンのおさらい • 画面を1つのオブジェクトとして定義するデザインパ ターン • テストコードを共通化するときの指針であり、テスト コード修正コストを小さくすることが目的 • Page Objectを適用させるときはこの目的を意識しな がら実装をする 76
  70. ⓒ 2020 DeNA Co., Ltd. 3. 長くテストコードを利用 し続けるには 77 1.

    Page Objectデザインパターンを適用する 2. CI環境でテストコードを動かし続ける 3. 検証結果をわかりやすく共有する 4. 不安定なテストを対策する 5. 実行時間を短縮させる 6. 開発・リリースフローに組み込む
  71. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 3-2. CI環境でテストコードを動かし続ける • CI 環境でUIテストを継続的に実行する − 自身以外の環境でもテストコードが動くことを確認する − 定期的な実行により、テストコードの安定性を確かめる − CI環境で実行することで運用に乗せやすくする • UIテストはユニットテストと比較すると、実行環境や外 部要因の影響をうけやすく壊れやすい • CI環境で継続して動かすことで安定性を確かめる 78
  72. ⓒ 2020 DeNA Co., Ltd. 3. 長くテストコードを利用 し続けるには 79 1.

    Page Objectデザインパターンを適用する 2. CI環境でテストコードを動かし続ける 3. テスト結果をわかりやすく共有する 4. 不安定なテストを対策する 5. 実行時間を短縮させる 6. 開発・リリースフローに組み込む
  73. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 3-3. テスト結果をわかりやすく共有する 80 • レポート形式でテスト結果を出力する • Slackにテストの成功と失敗を通知する • テスト失敗の原因がわかりやすいようにする − 失敗時のスクリーンショット・各種ログ・動画を保存する − 理由がわかりやすくなっていないと調査コストがかかる • 直近の結果だけでなく今までの履歴も保存する − どのテストケースが不安定になっているかを確認できる
  74. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 3-3. テスト結果をわかりやすく共有する 81 • Allure: 多機能なテストレポート表示ツール − http://allure.qatools.ru/ − JUnit互換のテスト結果XMLがあれば表示できる − Android向けadapterもあり(発展途上) https://github.com/TinkoffCreditSystems/allure-android # インストール $ brew install allure # テスト実行 $ ./gradlew connectedAndroidTest # テスト結果レポートをAllureで表示 $ allure serve \ app/build/outputs/androidTest-results/connected/
  75. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 3-3. テスト結果をわかりやすく共有する 82 • Allure Adapter − テスト結果レポートの内容をカスタマイズできる テスト失敗時にスクリーンショットを自動で追加 − Android Instrumented Test向けは発展途上 https://github.com/TinkoffCreditSystems/allure-android
  76. ⓒ 2020 DeNA Co., Ltd. 3. 長くテストコードを利用 し続けるには 83 1.

    Page Objectデザインパターンを適用する 2. CI環境でテストコードを動かし続ける 3. テスト結果をわかりやすく共有する 4. 不安定なテストを対策する 5. 実行時間を短縮させる 6. 開発・リリースフローに組み込む
  77. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 3-4. 不安定なテストの対策をする • たまに失敗する不安定なテストを放置せずに 対処を決める • 失敗が放置されている状況が続くと、テストの信頼性 もなくなってしまう • 不安定なテストを捨てるという選択肢も検討する 84
  78. ⓒ 2020 DeNA Co., Ltd. 3. 長くテストコードを利用 し続けるには 85 1.

    Page Objectデザインパターンを適用する 2. CI環境でテストコードを動かし続ける 3. テスト結果をわかりやすく共有する 4. 不安定なテストを対策する 5. 実行時間を短縮させる 6. 開発・リリースフローに組み込む
  79. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 3-5. 実行時間を短縮させる 86 • UIテストは実行時間が課題 − 自動テストが落ちたときの対応が面倒になる − 自動テストの完了が待てない − 実行に1時間以上かかるケースもある − その結果失敗しても放置される、使われなくなる... • 不必要なテストケースを作らない − テストピラミッドで推奨されるUIテストの割合は10% − UIテストとして追加が必要かを考える • テストを並列実行する
  80. ⓒ 2020 DeNA Co., Ltd. 3. 長くテストコードを利用 し続けるには 87 1.

    Page Objectデザインパターンを適用する 2. CI環境でテストコードを動かし続ける 3. テスト結果をわかりやすく共有する 4. 不安定なテストを対策する 5. 実行時間を短縮させる 6. 開発・リリースフローに組み込む
  81. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. 3-6. 開発やリリースフローに組み込む 88 • 実装したUIテストは実際に運用され、開発フローで利 用されることで意味のあるものになる • 開発・リリース時のフローに組み込むことで、 定期的にUIテストが実行される環境を作る − PR時に実行される − テストがパスしていないとPRのマージができない − 自動テストがすべてパスしていることがリリースの条件 etc...
  82. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. ここまでのまとめ 1. Page Objectデザインパターンを適用する − テストコードを共通化して変更に強くしよう 2. CI環境でテストコードを動かし続ける 3. テスト結果もわかりやすく共有する − テスト失敗の原因がすぐわかるようにしよう 4. 不安定なテストを対策する 5. 実行時間を短縮させる 6. 開発・リリースフローに組込む 89
  83. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. (付録) ショートカット一覧 Mac版 91 Action名 ショートカット 意味 Move Line Down Opt+Shift+↓ 選択範囲を下に移動する Change Signature Cmd+F6 メソッドシグネチャを変更する Move F6 (メソッドなどを) 別クラスや別ファイルに移動する Extract Function To Scope Opt+Shift+Cmd+M 選択範囲をメソッドとして抽出する Extract Parameter Opt+Cmd+P 選択箇所をメソッド引数にする Show Intention Actions Opt+Enter (空気を読んで) 必要なアクションを提案する Help>Find Action… で入力する名前 ショートカットを忘れたときに便利!
  84. ⓒ 2019 DeNA Co., Ltd. ⓒ 2019 DeNA Co., Ltd.

    ⓒ 2020 DeNA Co., Ltd. (付録) ショートカット一覧 Windows版 92 Action名 ショートカット 意味 Move Line Down Alt+Shift+↓ 選択範囲を下に移動する Change Signature Ctrl+F6 メソッドシグネチャを変更する Move F6 (メソッドなどを) 別クラスや別ファイルに移動する Extract Function To Scope Ctrl+Alt+Shift+M 選択範囲をメソッドとして抽出する Extract Parameter Ctrl+Alt+P 選択箇所をメソッド引数にする Show Intention Actions Alt+Enter (空気を読んで) 必要なアクションを提案する Help>Find Action… で入力する名前 ショートカットを忘れたときに便利!