【Flutter】Patrolでテストを書く（導入まで）

## はじめに
Flutterの[Finder](https://api.flutter.dev/flutter/flutter_test/Finder-class.html)や[WidgetTester](https://api.flutter.dev/flutter/flutter_test/WidgetTester-class.html)ではネイティブビュー（OSの権限許可ダイアログ等）を見つけたり操作することができません。
そこで、[patrol](https://patrol.leancode.co/)を使用して操作できるようにしたいと思います。
続き -> [【Flutter】Patrolでテストを書く（テスト編）](https://qiita.com/yyyy_yk/items/00ba856cdba50014d7f6)

本記事では`patrol`を導入し、iOSとAndroidのセットアップまで進めます。

## 環境
・flutter: 3.19.6
・dart: 3.3.4
・patrol: 3.6.1
・patrol_cli: 2.7.0

## `patrol`、`patrol_cli`のインストール

### `patrol`を追加

```
flutter pub add patrol --dev
```

※Android SDK versionは21以上

```android/app/build.gradle
android {
    // ----- 省略 ----- //
    defaultConfig {
        minSdkVersion 21
        // ----- 省略 ----- //
    }
```

### `pubspec.yaml`に`patrol`セクションを追加
・`app_name`、`package_name`、`bundle_id`はそれぞれ自身のアプリの値に置き換える

```pubspec.yaml
dependencies:
  # ...

dev_dependencies:
  # ...

patrol:
  app_name: My App
  android:
    package_name: com.example.myapp
  ios:
    bundle_id: com.example.MyApp
```

<details><summary>app_name等の確認方法</summary>

・`app_name`
```android/app/src/main/AndroidManifest.xml
<application
    android:label="アプリ名"
    android:name="${applicationName}"
    android:icon="@mipmap/ic_launcher">
```

```ios/Runner/Info.plist
<key>CFBundleName</key>
<string>アプリ名</string>
```

・`package_name`
```android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapp">
```

```android/app/build.gradle
android {
    // ----- 省略 ----- //
    defaultConfig {
        applicationId "com.example.myapp"
        // ----- 省略 ----- //
    }
}
```

・`bundle_id`は`Info.plist`で直接見ることができないので、`ios/Runner.xcworkspace`をXcodeで開いて確認

![スクリーンショット 2024-04-14 15.08.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906801/2b850989-1041-229b-a6d7-8670b14cb0fe.png)

</details>

### `patrol_cli`を追加

```
dart pub global activate patrol_cli
```

### `patrol`の`PATH`を通す（[patrol_cliのREADME](https://pub.dev/packages/patrol_cli#from-git)を参照）

・Dartバージョンを確認（記事作成時点では`2.18`以上が必要とあるが、`patrol_cli`のバージョンによって変わりそう？バージョンが足りなければ後続のコマンドでエラーになって分かるので、そこで再度確認するでもいいと思います）
```
dart --version
```

・[patrol](https://github.com/leancodepl/patrol)をcloneする
・cloneする場所はアプリ内である必要はないのでどこでもいい
```
git clone git@github.com:leancodepl/patrol.git
```

・`packages/patrol_cli`に移動
```
cd patrol/packages/patrol_cli
```

・以下を実行
```
dart pub global activate --source path .
```

・`patrol doctor`を実行して出力が全てグリーンになればOK
```
$ patrol doctor

Patrol doctor:
Patrol CLI version: 2.7.0
Flutter command: flutter 
  Flutter 3.19.5 • channel stable
Android: 
• Program adb found in /Users/username/Library/Android/sdk/platform-tools/adb
• Env var $ANDROID_HOME set to /Users/username/Library/Android/sdk
iOS / macOS: 
• Program xcodebuild found in /usr/bin/xcodebuild
• Program ideviceinstaller found in /usr/local/bin/ideviceinstaller
```

## Androidセットアップ

①`android/app/src/androidTest/java/com/example/myapp/`に移動。フォルダがなければ作成する。`com/example/myapp/`の部分は`pubspec.yaml`に記述した`package_name`に置き換える

②`MainActivityTest.java`という名前のファイルを作成し、以下のコードをコピペする
※`package com.example.myapp;`の部分は自身の`package_name`に書き換える

<details><summary>MainActivityTest.java</summary>

```MainActivityTest.java
package com.example.myapp; // replace "com.example.myapp" with your app's package

import androidx.test.platform.app.InstrumentationRegistry;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import pl.leancode.patrol.PatrolJUnitRunner;

@RunWith(Parameterized.class)
public class MainActivityTest {
    @Parameters(name = "{0}")
    public static Object[] testCases() {
        PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation();
        // replace "MainActivity.class" with "io.flutter.embedding.android.FlutterActivity.class" 
        // if your AndroidManifest is using: android:name="io.flutter.embedding.android.FlutterActivity"
        instrumentation.setUp(MainActivity.class);
        instrumentation.waitForPatrolAppService();
        return instrumentation.listDartTests();
    }

    public MainActivityTest(String dartTestName) {
        this.dartTestName = dartTestName;
    }

    private final String dartTestName;

    @Test
    public void runDartTest() {
        PatrolJUnitRunner instrumentation = (PatrolJUnitRunner) InstrumentationRegistry.getInstrumentation();
        instrumentation.runDartTest(dartTestName);
    }
}
```

</details>

③`build.gradle`の`defaultConfig`に以下の2行を追加

```android/app/build.gradle
android {
    // ----- 省略 ----- //
    defaultConfig {
        // ----- 省略 ----- //
        testInstrumentationRunner "pl.leancode.patrol.PatrolJUnitRunner"
        testInstrumentationRunnerArguments clearPackageData: "true"
    }
```

④`build.gradle`の`android`に以下を追加
```android/app/build.gradle
android {
    // ----- 省略 ----- //
    testOptions {
        execution "ANDROIDX_TEST_ORCHESTRATOR"
    }
```

⑤`build.gradle`の`dependencies`に以下を追加
```android/app/build.gradle
dependencies {
    androidTestUtil "androidx.test:orchestrator:1.4.2"
}
```

## iOSセットアップ

①`ios/Runner.xcworkspace`をXcodeで開く

②テストTARGETSがなければ作成する。注意点として言語は`Swift`ではなく`Objective-C`である必要がある

1. `File > New > Target...`で`UI Testing Bundle`を選択
1. `Product Name`を`RunnerUITests`にする
1. `Organization Identifier`を`pubspec.yaml`に記述した`bundle_id`と同じにする
1. `Language`を`Objective-C`に設定
1. `Target to be Tested`に`Runner`が設定されていること確認
1. `Finish`を選択

③`RunnerUITests.m`と`RunnerUITestsLaunchTests.m`が作成されるので、`RunnerUITestsLaunchTests.m`を選択して`delete > Move to Trash`で削除

④`Runner`と`RunnerUITests`の`Build Settings > iOS Deployment Target`を同じにする

![スクリーンショット 2024-04-14 17.22.23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906801/8cb5b14b-27d5-6b12-4a0d-1a6279d4b8c7.png)
![スクリーンショット 2024-04-14 17.22.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906801/1921ded0-4725-db2a-1375-f1ff7d9d80bc.png)

⑤`RunnerUITests.m`を以下に置き換える
```ios/RunnerUITests/RunnerUITests.m
@import XCTest;
@import patrol;
@import ObjectiveC.runtime;

PATROL_INTEGRATION_TEST_IOS_RUNNER(RunnerUITests)
```

⑥`Podfile`に`RunnerUITests`のtargetを追加
```ios/Podfile
target 'Runner' do
  # Do not change existing lines.

  target 'RunnerUITests' do
    inherit! :complete
  end
end
```

⑦アプリのルートに`integration_test/example_test.dart`を作成し、以下のコマンドを実行する。(`example_test.dart`は空のファイル)
```
flutter build ios --config-only integration_test/example_test.dart
```

⑧`ios`ディレクトリに移動し、以下のコマンドを実行
```
pod install --repo-update
```

⑨`Runner`の`info`を確認し、`RunnerUITests`の構成を`Runner`と同じにする
![スクリーンショット 2024-04-14 17.45.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906801/73861a36-e9f8-7250-2a26-287a8f00f7cd.png)

⑩`RunnerUITests > Build Phases`に移動し、左上の`＋`アイコンから`New Run Script Phase`を選択。`xcode_backend build`と`xcode_backend embed_and_thin`という名前の2つの新しい`Run Script Phase`を追加する

⑪新しく作成した`Run Script Phase`を以下の通りに並べ替える
![スクリーンショット 2024-04-14 17.51.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/906801/f494da88-5755-8c34-964c-3739dc0d5e43.png)

⑫`xcode_backend build`に以下を追加
```
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" build
```
⑬`xcode_backend embed_and_thin`に以下を追加
```
/bin/sh "$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh" embed_and_thin
```
⑭全てのschemeの`parallel execution`の設定を無効にする。設定の場所は[こちらの動画](https://www.youtube.com/watch?v=9LdEJR59fW4)を確認

⑮`RunnerUITests > Build Settings`に移動し、`User Script Sandboxing`を検索して`No`に設定する

## おわりに
長かったですが、以上でセットアップ完了です。エラーが発生した場合は[FAQ](https://patrol.leancode.pl/getting-started#faq)も参考になるかもしれません。
次回はテストを書いていこうと思います。

## 参考
・[Patrol Getting started](https://patrol.leancode.co/getting-started)
・[patrol_cli](https://pub.dev/packages/patrol_cli#installation)