テストエンジニアはFlutterの自動テストをPatrolでやったら面白そうと思った。

全国一千万人の自動テストエンジニアの皆さま、はじめまして。
皆さん、自動テストしてますか？
はじめまして、viviONのSETエンジニアのしたっぱです。

# はじめに
viviONでは一部のアプリをFlutterで開発しています。
Flutterで作られたアプリの自動テストのやり方を考えている中で、困ったことが結構あったので、お勉強の雑記として残していきます。

# 誰に向けてるのか
・Flutterのでのテスト実装で困ってる人
・appiumでFlutterのアプリを自動テストするのに限界を感じている人
・SETエンジニアが何で困ってるのか知りたい悪趣味な人（指差して笑いたい人）

# Flutterアプリの自動テストの現状
近年Flutterの自動テスト状況に関してまとめてくれている文章の中では、日本語の文献だとMagicPod社が提供している[『E2EテストのためのFlutterアプリ実装ガイドライン』] がアツいです。
前記文献に於いてはappiumで行うE2Eテストに重きが置かれています。
実際、自分もリリースモードでビルドされたアプリに対してのappiumで自動テストの実装を考える際に、このガイドラインにはかなりお世話になりました。

とはいえ、もっと踏み込んだE2Eテストはできないものか、と思ったときに調べて出会ったのが**patrol**でした。

## Patrolってなあに
https://patrol.leancode.co/

```
Patrol is a powerful, open-source testing framework for Flutter apps 
```
要約すると、dartでいい感じにUIテストが書けるフレームワークっぽいです。

おもしろそう。

## 実装
とりあえずサクッとFlutterの簡易アプリを構築してみましょう。その辺は、Flutterの公式ドキュメントとかを参照して行います。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273219/4ecbb182-39b0-4457-8f2d-671a49687408.png"width="200">


できました。Flutterエンジニアの皆様におかれましては、親の顔よりみた画面だと思います。
これに対して、appiumとPatrolでの実装を比較しながら検討していきます。

## 今回実装するテスト
そもそもこのアプリ自体が、下の方にある+ボタンを押下すると中央に表示されている数字が増えるという超簡単なアプリな為
| 手順 | 期待値 |
----|----
|+ボタンを1回押下する | 中央の数字が1になること

という趣旨のテストを書いていこうと思います。

### appium(Python)での実装
appiumでテストを実装するとき、いままではAppium Flutter Driverというモジュールが必要でした。
しかし、上記のモジュールは現状Flutter公式からは非推奨とされており、代わりにintegration testを使用するというのが定石になっているようです。（Flutter3系以降はiosでもandroidでもいままでよりも要素がとりやすくなったという情報が前記ガイドラインにも書かれていたので不要になったのかも？）


さっきのアプリをrelease modeでbuildしてappium inspectorで要素を取得してみましょう。

※今回のテストで必要な要素は、「+ボタン」と「中央の数字」の要素だけなので、そちらを参照します。

```bash
flutter run --release
```
buildして……。

実際のappium inspectorの画面がこちら。

**カウントした数字**
![スクリーンショット 2023-12-21 111652.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273219/235eca98-2ccf-b193-19ca-2bca7894f2f9.png)
**+ボタン**
![スクリーンショット 2023-12-21 111536.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273219/afc57459-3f5c-36a5-a017-d07e1235b958.png)
この場合のテストの実装
```python:test.py
def flutter_test(driver):
    driver.find_element(AppiumBy.XPATH,"/hierarchy/android.widget.FrameLayout/android.widget.LinearLayout/android.widget.FrameLayout/android.widget.FrameLayout/android.view.View/android.view.View/android.view.View/android.view.View/android.widget.Button").click()
    #もしくは
    #driver.find_element(AppiumBy.CLASS_NAME,"android.widget.Button").click()
    count_num =  driver.find_element(AppiumBy.ACCESSIBILITY_ID,"1").is_enabled()
    assert count_num is True

```
最悪の実装になりましたね。
まず、クソ長xpathは使いたくないので、上記リンクのガイドラインを参考に、accessibility idとして使えるsemantic nodeを追加できないか試してみます。

ここを
```dart: main.dart
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
```
こうして
```dart: main.dart
      floatingActionButton: Semantics(
        label: 'CountUpButton', 
        button: true, 
        child: FloatingActionButton(
          onPressed: _incrementCounter,
          tooltip: 'Increment',
          child: const Icon(Icons.add),
        ),
      ),
```
（buildして……。）

こうじゃ。

**inspectorの結果**
![スクリーンショット 2023-12-21 121321.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/273219/2bd72f1f-ee26-1c68-700e-0f2b4326c05f.png)
追加できてました。

これができると先ほどのテストコードがまだマシなものになります。
```python:test.py
def flutter_test(driver):
    driver.find_element(AppiumBy.ACCESSIBILITY_ID,"CountUpButton").click()
    count_num =  driver.find_element(AppiumBy.ACCESSIBILITY_ID,"1").is_enabled()
    assert count_num is True
```
こうなりました。
assertは若干微妙ではありますが、さっきのよりは数段いい感じですね。
（本当はcount_numに期待値を持たせたくないですが……。）

### Patrolでの実装
さて、Patrolでの実装はどうなるでしょう。
調べていくと、前記したintegration_testはどうやらOS側の表示の動作には対応していないようで、そこがPatrolではカバーされているとか。

https://patrol.leancode.co/native/overview
> Flutter's integration_test does a good job at providing basic support for integration testing Flutter apps. What it can't do is interaction with the OS your Flutter app is running on.

とりあえず
公式ドキュメントを参照して環境を構築しました。
https://patrol.leancode.co/getting-started

```dart:example_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/main.dart';
import 'package:patrol/patrol.dart';

void main() {
  patrolTest(
    'counter state is the same after going to home and going back',
    ($) async {
      await $.pumpWidgetAndSettle(const MyApp());
      await $(FloatingActionButton).tap();
      expect($('1').exists, equals(true));
    },
  );
}

```
動きました。
流石にDartで提供されているだけあってappiumより動作はサクサクしてる気がしますが毎回ビルドが走るのが少々厄介ですね。オプション等変更すればカットできたりするのでしょうか……。

## 総評 
流石にFlutter用で作られているだけあって動作がサクサクでした。
一部ツールでは、エミュレーターを使用したテストしかできないパターンがあったりするので、実機にビルドしてテストを動作させられるのもテストエンジニアとしてはかなり好印象です。

とはいえ、内部にテスト用の新しいパッケージを入れたりする必要がある（= 本番と同等の環境ではテストができない）等の欠点もあり、あくまで結合テスト用なのだなという印象も同時にあるのが本音です。

結合テストPhaseで深めのテストまでPatrolを実機で動かし、リリースするバイナリで行うE2Eテストでは簡単なテストをappiumで動かす　等のテスト計画をするといいかもしれません。

かなりのスピードで開発されているようなので、今後も動向をチェックしていきたいと思います。

~~Patrolの記事にしようとしたはずが、appiumの話の方が多くなっちゃったな……。~~

[『E2EテストのためのFlutterアプリ実装ガイドライン』]: https://magicpod.com/resources/flutter_guide/

# 参考

[ Flutterアプリの E2Eテストツール事情 - speakerdeck ]

[ Flutterアプリの E2Eテストツール事情 - speakerdeck ]: https://speakerdeck.com/mwakizaka/flutterapurino-e2etesutoturushi-qing

[ AppiumでFlutterアプリのテストを自動化する 実践編(Python) - Qiita ]

[ AppiumでFlutterアプリのテストを自動化する 実践編(Python) - Qiita ]: https://qiita.com/hiesiea/items/bf677a57200a1a24dda7
## 採用情報
viviONでは一緒に働いてくれるSETエンジニア・QAエンジニアを募集しています。
二次元コンテンツやDLsiteが好きなNerdなテストエンジニアの皆さん、一緒に働きませんか？

※普段はPythonでappiumやplaywrightを使用してテストコードを書いてます！


https://vivion.jp/recruit/