XCUITestのつらさを乗り越えて、iOSアプリにUITestを導入する

53e2d354b3299d64a54af680865516d5?s=47 Sato Takeshi
September 21, 2020

 XCUITestのつらさを乗り越えて、iOSアプリにUITestを導入する

53e2d354b3299d64a54af680865516d5?s=128

Sato Takeshi

September 21, 2020
Tweet

Transcript

  1. XCUITestのつらさを
 乗り越えて、
 iOSアプリにUITestを導入する
 iOSDC Japan 2020
 2020 年 9 月

    21 日 
 #iosdc

  2. Who am I
 • Name
 • 佐藤剛士(さとうたけし)
 • Company
 •

    Merpay, Inc.(2019/01 ~)
 • Role
 • Software Engineer (iOS)
 • Account
 • Twitter: @hatakenokakashi
 • Facebook: 佐藤剛士
 • GitHub: SatoTakeshiX

  3. 今日話すこと
 • UITestのつらさ
 • メルペイにUITestを導入した経緯
 • XCUITest入門
 • より実践的なUIテスト
 •

    継続的に運用を続けるために

  4. UITestのつらさ


  5. いつの間にか壊れている


  6. UIは変わりゆくもの-メルペイページ変遷
 2019/02 〜 2019/11 〜 2020/04 〜

  7. UITestでも
 CI導入が必要
 壊れたテストを
 素早く
 発見する
 トリガー テスト ビルド

  8. メルペイにUITestを導入した経緯


  9. メルペイコードと
 メルカリアプリ
 SDK

  10. メルカリリリースタイムライン
 リリース
 ブランチ
 カット
 App Store
 リリース
 リグレッション
 テスト
 App

    Store
 申請
 2営業日
 2営業日
 1営業日

  11. メルカリリリースタイムライン
 リリース
 ブランチ
 カット
 App Store
 リリース
 リグレッション
 テスト
 App

    Store
 申請
 2営業日
 2営業日
 1営業日
 修正

  12. リグレッションテストとは?
 • プログラム変更に伴い、予想外の影響が現れていないかを確認す るテスト
 • 回帰テスト、退行テストともいう


  13. メルカリ側ではUITestは導入済み
 https://engineering.mercari.com/blog/entry/2018-08-07-123000/

  14. メルペイにUITest導入の背景
 • 2019年2月にサービスイン以降、新機能開発が続く
 • 金融サービスを提供しているため品質は重要
 • モバイルアプリのテストは手動テストのみ
 • QAの工数がひっ迫する状況が続く
 •

    QA工数を減らす手段の一つとしてUITestを導入

  15. UITestの対象
 新機能をUITestするか? 既存機能をUITestするか?

  16. UITestの対象
 新機能をUITestするか? 既存機能をUITestするか? メルペイは
 こちらから


  17. メルペイは既存機能をUITest化
 • リグレッションテストは二週間ごとに必ず実施される
 • 既存機能は安定稼働されていることが望まれる
 • UITest化は繰り返し実施されるテストと安定稼働している機能にた いして行うと効率が良い


  18. メルカリリリースタイムライン
 リリース
 ブランチ
 カット
 App Store
 リリース
 リグレッション
 テスト
 App

    Store
 申請
 2営業日
 2営業日
 1営業日
 定期的な作業を自動化で効率化!

  19. メルペイUITestの進捗
 リグレッションテスト項目 
 自動化対象
 CircleCIパス
 項目
 300項目
 200項目
 40項目


  20. XCUITest入門


  21. XCUITestの重要なクラス
 XCUIApplication
 • アプリを起動/停止するプロキシ
 • 環境変数を指定可能
 XCUIElementQuery
 • UI要素を検索するクエリー 


    • UI要素はXCUIElementとして取得
 XCUIElement
 • 検索されたUI要素 
 • UI操作や状態取得が可能 

  22. テスト対象アプリ


  23. UI要素検索
 tabBars/buttonsが XCUIElementQuery

  24. UI要素検索
 「メルペイ」という タブを検索

  25. UI要素取得
 firstMatchで最初に ヒットした XCUIElementを取得

  26. UI操作
 タブをタップ メルペイ画面へ

  27. UI要素検索
 「メルペイスマート払 い残枠」ラベル検索

  28. 表示チェック
 ラベルが 「¥199,000」で あるか判定

  29. Accessibility Identifier
 • XCUIApplicationにはUI要素を添字で検索できるXCUIElementQuery プロパティがたくさんある
 • staticTextsはUILabelを検索できる
 • Accessibility Identifierプロパティ値か、ラベルの文字列で検索がで

    きる。

  30. Accessibility Identifier


  31. Accessibility Identifier
 積極的にAccessibilityIdentifierを入れるのがオススメ


  32. より実践的なUIテスト


  33. Page Objectの導入


  34. もう一つテストを作ってみよう
 「利用履歴」セルを タップ

  35. テストメソッドにUI操作を記述する弊害
 複数テストで UI操作が重複

  36. テストと画面を分ける考え方
 Appiumが提唱するデザインパターン
 Page Object Pattern


  37. Page Object Patternとは
 • 画面単位(一部も可)でクラスを定義する
 • UI要素やその操作メソッドを定義する
 • UI要素はprivateにし、外部に公開しない
 •

    PageObjectの内部にアサーションは書かない
 • メソッドはPage Objectを返すとメソッドチェーンできて便利

  38. Page Object Patternメリット
 可読性の向上
 • 画面とシナリオを分離
 DRY原則を保つ
 • 同じ画面操作をまとめる
 変更容易性向上


    • UI要素変更 -> Page Objectのみ

  39. Page Object実装:PageObjectableプロトコル


  40. Page Object実装:PageObjectableプロトコル
 Accessibility Identifier(=A11y)を まとめる型

  41. Page Object実装:PageObjectableプロトコル
 アプリを取得

  42. Page Object実装:PageObjectableプロトコル
 画面が存在するかを表す

  43. Page Object実装:PageObjectableプロトコル
 ページのタイトル

  44. Page Object実装:PageObjectableプロトコル
 UI要素が存在するか判定する メソッド

  45. PageObjectableデフォルト実装
 XCUIApplicationを返す

  46. PageObjectableデフォルト実装
 pageTitleが存在すれば その画面は存在する

  47. PageObjectableデフォルト実装
 引数の要素が存在すれば trueを返す

  48. Page Object PatternでUIテスト


  49. 設定画面が表示されるかテスト


  50. HomePage


  51. HomePage
 Accessibility Identifierをまとめる (ラベル名でもOK)

  52. HomePage
 UI要素をまとめる

  53. HomePage
 UI操作をまとめる

  54. MerpayPage


  55. MerpayPage


  56. MerpayPage
 Accessibility Identifierまとめる (ここではタイトルと設定セルのラ ベル名)

  57. MerpayPage
 タイトルと設定画面セルの UI要素

  58. MerpayPage
 設定画面へ遷移

  59. Merpay
 SettingsPage


  60. MerpaySettingsPage


  61. MerpaySettingsPage
 画面タイトルのみ定義する

  62. MerpaySettingsPage
 PageObjectableのデフォルト実装で existsプロパティから画面の存在確 認がすぐできる

  63. テストコード


  64. テストコード
 ホーム画面から メルペイ画面に行って 設定画面に行く

  65. テストコード
 設定画面が表示されているかを チェック! 設定画面 あった!

  66. テストコード
 テストコード上での可読性が上がっている!

  67. Tipsと Workaround
 知っておくと便利な
 XCUITestのTips集
 • RunActivityでテスト結果をグルー プ化
 • アニメーション無効とカスタムトラ ンジション


    • 同じIdentifierで違うLabelのUIを検 索する
 • iOS13とiOS12でUI階層が異なる 問題

  68. RunActivityで
 テスト結果グループ化


  69. Xcodeでテスト結果を確認


  70. RunActivityでテスト結果をグループ化


  71. RunActivityでテスト結果をグループ化
 runActivity: テストログをグループ化

  72. RunActivityでテスト結果をグループ化
 テスト番号と具体的に何をするかを 名前をつける

  73. RunActivityでテスト結果をグループ化
 クロージャーに実行処理を書く

  74. RunActivityでテスト結果をグループ化
 ネストしてもOK 上から順番に呼ばれる

  75. テスト結果の表示変化


  76. テスト結果の表示変化
 テストメソッド名

  77. テスト結果の表示変化
 1番目のrunActivity名が表示

  78. テスト結果の表示変化
 2番目のrunActivityの名前が表示 テストの結果もネストされている

  79. テスト結果の表示変化
 ログがグループ化された

  80. アニメーション無効
 と
 カスタムトランジション


  81. • UITestの有名Tipsとしてアプリのアニメーション無効がある
 • アニメーションを無効にすることで実行時間を短くできる
 アニメーション無効とカスタムトランジション
 
 


  82. アニメーション無効とカスタムトランジション
 
 
 • UITestの有名Tipsとしてアプリのアニメーション無効がある
 • アニメーションを無効にすることで実行時間を短くできる
 アプリ側のコード

  83. アニメーション無効とカスタムトランジション
 
 
 • UITestの有名Tipsとしてアプリのアニメーション無効がある
 • アニメーションを無効にすることで実行時間を短くできる
 isUITestっていう引数があるなら アニメーション無効

  84. アニメーション無効とカスタムトランジション
 
 
 • UITestの有名Tipsとしてアプリのアニメーション無効がある
 • アニメーションを無効にすることで実行時間を短くできる
 UITest側でlaunchArgumentsに 起動時に渡す引数を追加 一見有効なTipsだが、、、


  85. アニメーション無効とカスタムトランジション
 
 
 • UIViewControllerTransitioningDelegateで実装した独自のトランジ ションとUIView.setAnimationsEnabled(false)の組み合わせが悪いこ とが判明
 • Viewが表示されずUI操作ができなくなる


  86. アニメーション無効とカスタムトランジション
 
 
 画面に表示はない

  87. アニメーション無効とカスタムトランジション
 
 
 po XCUIApplication() するとUI要素は存在している

  88. アニメーションONでUITestを実行
 
 


  89. 同じIdentifierで
 違うLabelのUIを
 検索する


  90. 同じIdentifierで違うLabelのUIを検索する
 
 
 「Identifier」
 com.merpay.merpay_mercari_wallet_kit.wallet_history_button_view.title_label 
 「label」
 売上金、ポイント、メルペイスマート払い 


  91. Matchingを使う
 
 


  92. iOS13とiOS12で
 UI階層が異なる問題


  93. iOS13とiOS12でUI階層が異なる問題
 
 
 iOS 12 Button, 0x6000024e3100, {{203.0, 475.5}, {145.0,

    29.0}}, label: '振込申請とスケジュール ’ iOS 13 Button, 0x600000754700, {{203.0, 475.5}, {145.0, 29.0}}, label: '振込申請とスケジュール ' StaticText, 0x6000007547e0, {{203.0, 481.5}, {145.0, 17.0}}, label: '振込申請とスケジュール ' iOS 13にはStaticText要素が取得できる
  94. iOS13とiOS12でUI階層が異なる問題
 • メルペイではOSバージョンごとにUITestをサポートするのがコストが 高いと判断
 • UITestの対象をiOS 13以上として、場合分けの処理は実装していな い
 • UITestをどのOSバージョンまで対応するかはそれぞれのプロジェク

    トで考慮が必要

  95. 継続的に運用を続けるために


  96. 通常のCI
 Build UnitTest push
 pull
 request
 作成


  97. UITestはめちゃくちゃ時間かかる
 テストケース:91件
 並列化実行あり


  98. 適切なトリガーはなんだろう?


  99. 夜間実行
 GitHub Actions
 Labelトリガー


  100. 夜間実行


  101. GitHub Actions Labelトリガー


  102. GitHub Actions Labelトリガー
 Pull Requestが「作成されたら」 「ラベルが追加されたら」 「追加でプッシュされたら」

  103. GitHub Actions Labelトリガー
 指定のラベルがあった場合

  104. GitHub Actions Labelトリガー
 CircleCIパイプラインを実行

  105. テスト結果をCircleCIで確認


  106. XCTestHTMLReportを使おう
 • XcodeのUnit Test, UITestの 結果をHTMLに変換してくれ るツール
 • Xcode 11から登場したResult

    Bundleも整形可能
 • CircleCI上で出力された xcresultファイルを変換する
  
 https://github.com/TitouanVanBelle/XCTestHTMLReport
  107. config.yml


  108. config.yml
 store_artifacts に指定したディレクトリが アップロードされる always: エラーが起きてもアップする

  109. config.yml
 fastlaneでUITestを実行

  110. fastlaneでUITest実行


  111. fastlaneでUITest実行
 Result Bundleの出力先を指定

  112. fastlaneでUITest実行
 Result Bundleと出力先のディレクトリ を指定

  113. fastlaneでUITest実行
 Result BundleをHTMLファイルに 変換

  114. テスト結果がCircleCI上で確認できる


  115. まとめ


  116. まとめ
 • UITestにもCIを導入して失敗を検知するようにしよう
 • メルペイではQA効率化の一環でUITestを導入した
 • XCUITestによるUITestの実装の流れ
 • 実践的なUITestの実装方法やTips
 •

    UITestで継続的な運用をする方法

  117. 参考文献
 • Appleの公式ドキュメント「Testing Your Apps in Xcode」の紹介
 • iOSアプリ開発自動テストの教科書
 •

    SeleniumHQ/selenium Page Objects
 • Swift での UI テストの雑なまとめ
 • https://github.com/TitouanVanBelle/XCTestHTMLReport

  118. おまけ


  119. システムアラート操作


  120. システムアラート操作
 
 
 位置情報許可アラートを操作!

  121. システムアラート操作
 
 
 XCUIApplication()では システムアラートの要素が 取得できない

  122. システムアラート操作
 
 
 bundleIdentifierに"com.apple.springboard" でシステムのUI要素を取得する