iOSでFlankを使ってFirebase Test LabでUITest


テストの分割などが可能なツール、FlankをiOSのUITestで導入してみます。

公式githubのREADME.mdももちろん見てください。
https://github.com/Flank/flank

Bitriseでの利用は別記事で触れています。

# 環境

- macOS Mojave 10.14.6（18G103）
- java version "1.8.0_241"
- Flank v8.1.0
- gcloud Google Cloud SDK 276.0.0, bq 2.0.52, core 2020.01.10, gsutil 4.47


# 環境の準備

私はおもむろに`Contributing`の項目の設定をはじめてしまいました。
使うだけの人は`iOS example`の項目からはじめましょう。


## java

ここでは`jdk-8u241-macosx-x64.dmg`を使いインストールしました。
https://www.oracle.com/java/technologies/javase-jdk8-downloads.html

```shell
$ java -version
java version "1.8.0_241"
Java(TM) SE Runtime Environment (build 1.8.0_241-b07)
Java HotSpot(TM) 64-Bit Server VM (build 25.241-b07, mixed mode)
```

java8にこだわらなくても良いと思いますがjava13だと動きませんでした。


## gcloud CLI

インストールがまだの方はこちらから
https://cloud.google.com/sdk/gcloud?hl=ja

```shell
$ gcloud --version
Google Cloud SDK 276.0.0
bq 2.0.52
core 2020.01.10
gsutil 4.47
```

## Google App Credentials

Google Credentials JSONを用意します。
ない方は以下から作成できます。p12とjsonが選べる場合はjson形式でokです。
https://console.cloud.google.com/apis/credentials/serviceaccountkey

storageへ書き込む権限が必要です。

ここでは以下のファイルがダウンロードされました。
`kanari3-develop-ios-55a55aaa55aa.json`

環境変数にexportします。

```shell
# ex: (例)
export GOOGLE_APPLICATION_CREDENTIALS='保存したパス/kanari3-develop-ios-55a55aaa55aa.json'
```

毎回流すのが面倒な方は`.bash_profile`などに書いておきましょう。


## iOS側

UITestが動くアプリを準備してください。

確認しておく項目は以下です。

- UITest用のScheme名

<img width="810" alt="xcode-scheme.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275986/d68f4cb2-95eb-eb68-acf2-0c5efa8d7340.png">

ここでは`UITestSampleUITests`となっています。



# How-to-Use Flank

流れは以下のような感じです。

- UITest用アプリのBuild
- UITest用アプリのArchive
- Flankのclone
- ios.ymlを編集しておく
- Flankをビルド
- ビルドしたFlankのjarを実行



## UITest用アプリのBuild

※ XCUITestでは、アプリ本体と別に、テスト用アプリをビルドして使います。

- XcodeのIDEではなくコマンドでビルドします。
- テストしたいアプリのルートで以下を実行してビルドします。
- ここではDerivedDataをプロジェクト以下に置きました。(commitしないようにしてください)
- MyProject、MyUITestsSchemeは適時読み替えてください。

```shell
xcodebuild -workspace MyProject.xcworkspace \
  -scheme MyUITestsScheme \
  -derivedDataPath MyDeriverdData \
  -sdk iphoneos build-for-testing
```


## UITest用アプリのArchive

以下のコマンドでzip化します。

```shell
cd MyDeriverdData/Build/Products ; \
zip -r MyTestApp.zip Debug-iphoneos MyUITestsScheme_iphoneos*-arm64e.xctestrun
```

`MyUITestsScheme_iphoneos*-arm64e.xctestrun`の箇所はファイル名なので補完を効かせて入力しましょう


## Flankのclone

cloneして中に移動します。

```
git clone git@github.com:Flank/flank.git && cd flank
```


## ios.ymlを編集しておく

`./test_runner/flank.ios.yml`

まずは、最低限の設定をしてFlankでのテストを通しましょう。
Line番号はFlankのversionにより変わると思うので大体です。
ベースは書かれているのでコメントアウトを外して追記します。

- L:41 `test:`

アーカイブのパスです。

相対パスや絶対パスで入力します。
実運用ではflank.ios.ymlをアプリプロジェクトに内包すればパスは書きやすくなるかも知れませんね。

```
# L:41 ex:
test: /works/UITestSample/MyTests/Build/Products/MyTests.zip
```

- L:47 `xctestrun-file:`

xctestrunファイルのパスです。

```
# L:47 ex:
xctestrun-file: /works/UITestSample/MyTests/Build/Products/UITestSampleUITests_iphoneos12.3-arm64e.xctestrun
```

DeploymentTargetにより`12.3`の箇所が変わるので実際のファイル名を確認してください。


## Flankをビルド

```shell
cd test_runner/
./gradlew clean build fatJar
```

成功したときのログは以下

```
BUILD SUCCESSFUL in 1m 17s
16 actionable tasks: 16 executed
```

## ビルドしたFlankのjarを実行

Flankを使ってFirebase Test LabでUITestを実行します。


```shell
java -jar ./build/libs/flank-*.jar firebase test ios run
```

`flank-*`の箇所は私の環境では`flank-SNAPSHOT.jar`でした。

実行時のログは以下


```
$ java -jar ./build/libs/flank-*.jar firebase test ios run
IosArgs
    gcloud:
      results-bucket: test-lab-4d0240uptt2s2-j0tb4zpdrhnc5
      results-dir: null
      record-video: false
      timeout: 15m
      async: false
      results-history-name: null
      # iOS gcloud
      test: /work/UITestSample/MyTests/Build/Products/MyTests.zip
      xctestrun-file: /work/UITestSample/MyTests/Build/Products/UITestSampleUITests_iphoneos13.2-arm64e.xctestrun
      xcode-version: null
      device:
        - model: iphone8
          version: 12.0
          locale: en
          orientation: portrait
      num-flaky-test-attempts: 0

    flank:
      max-test-shards: 1
      shard-time: -1
      num-test-runs: 1
      smart-flank-gcs-path:
      smart-flank-disable-upload: false
      test-targets-always-run:

      files-to-download:

      # iOS flank
      test-targets:

      disable-sharding: false
      project: kanari3-develop-ios
      local-result-dir: results
RunTests
  Uploading MyTests.zip .
Found xctest: /work/UITestSample/MyTests/Build/Products/Debug-iphoneos/UITestSampleUITests-Runner.app/PlugIns/UITestSampleUITests.xctest
isMacOS = true (mac os x)
nm -U "/work/UITestSample/MyTests/Build/Products/Debug-iphoneos/UITestSampleUITests-Runner.app/PlugIns/UITestSampleUITests.xctest/UITestSampleUITests"
nm -gU "/work/UITestSample/MyTests/Build/Products/Debug-iphoneos/UITestSampleUITests-Runner.app/PlugIns/UITestSampleUITests.xctest/UITestSampleUITests" | xargs -s 262144 xcrun swift-demangle

  Smart Flank cache hit: 0% (0 / 1)
  Shard times: 120s

  1 test / 1 shard

  Uploading UITestSampleUITests_iphoneos13.2-arm64e.xctestrun .
  1 matrix ids created in 0m 5s
  https://console.developers.google.com/storage/browser/test-lab-4d0240uptt2s2-j0tb4zpdrhnc5/2020-03-06_01-58-13.697000_pdKG/

PollMatrices
  0m  1s matrix-71ucqbcx81avk iphone8-12.0 VALIDATING
  0m 13s matrix-71ucqbcx81avk iphone8-12.0 PENDING
  0m 25s matrix-71ucqbcx81avk iphone8-12.0 Starting attempt 1.
  0m 25s matrix-71ucqbcx81avk iphone8-12.0 Checking Internet connection...
  0m 25s matrix-71ucqbcx81avk iphone8-12.0 RUNNING
  0m 56s matrix-71ucqbcx81avk iphone8-12.0 Internet connection stable!
  1m  9s matrix-71ucqbcx81avk iphone8-12.0 Started device logs task
  2m 27s matrix-71ucqbcx81avk iphone8-12.0 Stopped device logs task
  2m 36s matrix-71ucqbcx81avk iphone8-12.0 Done. Test time = 60 (secs)
  2m 36s matrix-71ucqbcx81avk iphone8-12.0 Starting results processing. Attempt: 1
  2m 36s matrix-71ucqbcx81avk iphone8-12.0 Completed results processing. Time taken = 4 (secs)
  2m 36s matrix-71ucqbcx81avk iphone8-12.0 FINISHED
  2m 36s matrix-71ucqbcx81avk FINISHED

FetchArtifacts
  .
  Updating matrix file

CostReport
  Physical devices
    $0.08 for 1m

MatrixResultsReport
  1 / 1 (100.00%)

```

### ログ解説

ここでは成功率は1/1で100% PASSとなっていますが、Matrix単位なので、UITestが複数ケースあっても1/1となります。

GCPのストレージにMyTests.zipがアップロードされているのが確認できると思います。

金額は$0.08課金されています。

実行デバイスはFirebase Test Labのdefaultデバイスが自動的に選択されます。
確認したい方は以下

```shell
$ gcloud firebase test ios models list
┌─────────────┬───────────────────────┬─────────────────────┬──────────────────────────────────┐
│   MODEL_ID  │          NAME         │    OS_VERSION_IDS   │               TAGS               │
├─────────────┼───────────────────────┼─────────────────────┼──────────────────────────────────┤
│ ipad5       │ iPad (5th generation) │ 11.2,12.0           │ deprecated=11.2                  │
│ ipadmini4   │ iPad mini 4           │ 11.2,12.0           │ deprecated=11.2                  │
│ ipadpro_105 │ iPad Pro (10.5-inch)  │ 11.2                │ deprecated=11.2                  │
│ iphone11    │ iPhone 11             │ 13.3                │                                  │
│ iphone11pro │ iPhone 11 Pro         │ 13.3                │                                  │
│ iphone6     │ iPhone 6              │ 11.4,12.2           │ deprecated=11.4                  │
│ iphone6s    │ iPhone 6s             │ 10.3,11.2,11.4,12.0 │ deprecated=10.3, deprecated=11.2 │
│ iphone7     │ iPhone 7              │ 11.2,11.4,12.0,12.3 │ deprecated=11.2                  │
│ iphone7plus │ iPhone 7 Plus         │ 11.2,11.4,12.0      │ deprecated=11.2                  │
│ iphone8     │ iPhone 8              │ 11.2,11.4,12.0      │ deprecated=11.2, default         │
│ iphone8plus │ iPhone 8 Plus         │ 11.2,11.4,12.0,12.3 │ deprecated=11.2                  │
│ iphonese    │ iPhone SE             │ 11.2,11.4,12.0,12.3 │ deprecated=11.2                  │
│ iphonex     │ iPhone X              │ 11.2,11.4,12.0,12.3 │ deprecated=11.2                  │
│ iphonexr    │ iPhone XR             │ 13.2                │                                  │
│ iphonexs    │ iPhone XS             │ 12.0,12.1,12.3      │                                  │
│ iphonexsmax │ iPhone XS Max         │ 12.0,12.1,12.3      │                                  │
└─────────────┴───────────────────────┴─────────────────────┴──────────────────────────────────┘
```


## ステップアップ

Firebase Test LabのUITest録画機能を使ってみましょう

### 設定

ios.ymlのL:18の`record-video:`をtrueに設定します。

```
record-video: true
```

### 実行結果

ログの`https://console.developers.google.com/storage/browser/`の箇所にアップロードされたファイルが格納されています。

動画ファイルはここでは以下に保存されていました。
`shard_0/iphone8-12.0-en-portrait/video.mp4`


## テスト失敗時

ログは以下のようになります。

```
MatrixResultsReport
  0 / 1 (0.00%)
  1 matrices failed

HtmlErrorReport written to /work/flank/test_runner/results/flank-uitest/HtmlErrorReport.html
Error: Matrix failed: matrix-71ucqbcx81avk FINISHED failure  https://console.firebase.google.com/project/kanari3-develop-ios/testlab/histories/bh.49d8d135811054f7/matrices/8560953674081399197/executions/bs.9f408b536f2a9f54
```

テストレポートのURLが表示されていて親切ですね。

リンクに飛ぶと以下のような画面になります。

![ftl1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275986/92cbe175-85fb-93dd-25aa-0d977729cace.png)

動画タブでは動画が再生できます。

![ftl3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275986/3b1c1427-35d5-1ad6-2d6d-20405491ac65.png)


`textExample()`のテストケースを選択すると個別のエラーログが見れます。

![ftl2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/275986/19a3c03b-b458-da2d-0d59-42f76f21fc81.png)

TestCaseに`XCFail("わざと失敗させました")`を挿入してみました。

# 管理

GCPのバケットがどんどん溜まっていくのでディレクトリを設定しましょう。
必要なファイルが埋もれるとまとめて消すときに事故のもとです。

```
# L:12 ex
results-dir: flank-uitest
```

Cloud Storageの無料枠 (5GB) も意識しましょう。


# トラブルシューティング

## GOOGLE_APPLICATION_CREDENTIALSが読めない

`~/` に置くとFlankから読めないことがあるようです。


# おわり

随時追記します。
シャードを分けた並列テストについては別記事を書く予定です！

次はBitriseからFlankを使う方法について触れます。
[BitriseでもFlankを使ってFirebase Test LabでUITestしたい]
(https://qiita.com/kanari3/items/a96bcb082548f7500a5a)