Slide 1

Slide 1 text

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


Slide 2

Slide 2 text

Who am I
 ● Name
 ● 佐藤剛士(さとうたけし)
 ● Company
 ● Merpay, Inc.(2019/01 ~)
 ● Role
 ● Software Engineer (iOS)
 ● Account
 ● Twitter: @hatakenokakashi
 ● Facebook: 佐藤剛士
 ● GitHub: SatoTakeshiX


Slide 3

Slide 3 text

今日話すこと
 ● UITestのつらさ
 ● メルペイにUITestを導入した経緯
 ● XCUITest入門
 ● より実践的なUIテスト
 ● 継続的に運用を続けるために


Slide 4

Slide 4 text

UITestのつらさ


Slide 5

Slide 5 text

いつの間にか壊れている


Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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


Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

メルカリリリースタイムライン
 リリース
 ブランチ
 カット
 App Store
 リリース
 リグレッション
 テスト
 App Store
 申請
 2営業日
 2営業日
 1営業日


Slide 11

Slide 11 text

メルカリリリースタイムライン
 リリース
 ブランチ
 カット
 App Store
 リリース
 リグレッション
 テスト
 App Store
 申請
 2営業日
 2営業日
 1営業日
 修正


Slide 12

Slide 12 text

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


Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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


Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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


Slide 17

Slide 17 text

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


Slide 18

Slide 18 text

メルカリリリースタイムライン
 リリース
 ブランチ
 カット
 App Store
 リリース
 リグレッション
 テスト
 App Store
 申請
 2営業日
 2営業日
 1営業日
 定期的な作業を自動化で効率化!


Slide 19

Slide 19 text

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


Slide 20

Slide 20 text

XCUITest入門


Slide 21

Slide 21 text

XCUITestの重要なクラス
 XCUIApplication
 ● アプリを起動/停止するプロキシ
 ● 環境変数を指定可能
 XCUIElementQuery
 ● UI要素を検索するクエリー 
 ● UI要素はXCUIElementとして取得
 XCUIElement
 ● 検索されたUI要素 
 ● UI操作や状態取得が可能 


Slide 22

Slide 22 text

テスト対象アプリ


Slide 23

Slide 23 text

UI要素検索
 tabBars/buttonsが XCUIElementQuery

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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


Slide 30

Slide 30 text

Accessibility Identifier


Slide 31

Slide 31 text

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


Slide 32

Slide 32 text

より実践的なUIテスト


Slide 33

Slide 33 text

Page Objectの導入


Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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


Slide 37

Slide 37 text

Page Object Patternとは
 ● 画面単位(一部も可)でクラスを定義する
 ● UI要素やその操作メソッドを定義する
 ● UI要素はprivateにし、外部に公開しない
 ● PageObjectの内部にアサーションは書かない
 ● メソッドはPage Objectを返すとメソッドチェーンできて便利


Slide 38

Slide 38 text

Page Object Patternメリット
 可読性の向上
 ● 画面とシナリオを分離
 DRY原則を保つ
 ● 同じ画面操作をまとめる
 変更容易性向上
 ● UI要素変更 -> Page Objectのみ


Slide 39

Slide 39 text

Page Object実装:PageObjectableプロトコル


Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

Page Object PatternでUIテスト


Slide 49

Slide 49 text

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


Slide 50

Slide 50 text

HomePage


Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

HomePage
 UI要素をまとめる

Slide 53

Slide 53 text

HomePage
 UI操作をまとめる

Slide 54

Slide 54 text

MerpayPage


Slide 55

Slide 55 text

MerpayPage


Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

MerpayPage
 設定画面へ遷移

Slide 59

Slide 59 text

Merpay
 SettingsPage


Slide 60

Slide 60 text

MerpaySettingsPage


Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

テストコード


Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Tipsと Workaround
 知っておくと便利な
 XCUITestのTips集
 ● RunActivityでテスト結果をグルー プ化
 ● アニメーション無効とカスタムトラ ンジション
 ● 同じIdentifierで違うLabelのUIを検 索する
 ● iOS13とiOS12でUI階層が異なる 問題


Slide 68

Slide 68 text

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


Slide 69

Slide 69 text

Xcodeでテスト結果を確認


Slide 70

Slide 70 text

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


Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

テスト結果の表示変化


Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

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


Slide 81

Slide 81 text

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


Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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


Slide 85

Slide 85 text

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


Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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


Slide 89

Slide 89 text

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


Slide 90

Slide 90 text

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


Slide 91

Slide 91 text

Matchingを使う
 
 


Slide 92

Slide 92 text

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


Slide 93

Slide 93 text

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要素が取得できる

Slide 94

Slide 94 text

iOS13とiOS12でUI階層が異なる問題
 ● メルペイではOSバージョンごとにUITestをサポートするのがコストが 高いと判断
 ● UITestの対象をiOS 13以上として、場合分けの処理は実装していな い
 ● UITestをどのOSバージョンまで対応するかはそれぞれのプロジェク トで考慮が必要


Slide 95

Slide 95 text

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


Slide 96

Slide 96 text

通常のCI
 Build UnitTest push
 pull
 request
 作成


Slide 97

Slide 97 text

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


Slide 98

Slide 98 text

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


Slide 99

Slide 99 text

夜間実行
 GitHub Actions
 Labelトリガー


Slide 100

Slide 100 text

夜間実行


Slide 101

Slide 101 text

GitHub Actions Labelトリガー


Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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

Slide 105

Slide 105 text

テスト結果をCircleCIで確認


Slide 106

Slide 106 text

XCTestHTMLReportを使おう
 ● XcodeのUnit Test, UITestの 結果をHTMLに変換してくれ るツール
 ● Xcode 11から登場したResult Bundleも整形可能
 ● CircleCI上で出力された xcresultファイルを変換する
  
 https://github.com/TitouanVanBelle/XCTestHTMLReport

Slide 107

Slide 107 text

config.yml


Slide 108

Slide 108 text

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

Slide 109

Slide 109 text

config.yml
 fastlaneでUITestを実行

Slide 110

Slide 110 text

fastlaneでUITest実行


Slide 111

Slide 111 text

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

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

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

Slide 114

Slide 114 text

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


Slide 115

Slide 115 text

まとめ


Slide 116

Slide 116 text

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


Slide 117

Slide 117 text

参考文献
 ● Appleの公式ドキュメント「Testing Your Apps in Xcode」の紹介
 ● iOSアプリ開発自動テストの教科書
 ● SeleniumHQ/selenium Page Objects
 ● Swift での UI テストの雑なまとめ
 ● https://github.com/TitouanVanBelle/XCTestHTMLReport


Slide 118

Slide 118 text

おまけ


Slide 119

Slide 119 text

システムアラート操作


Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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