Flutter と ReactNative でのE2Eテスト導入比較

# 1. はじめに

## 1-1. E2Eテストの目的と重要性
エンドツーエンドテスト（E2Eテスト）は、開発されたアプリケーションが実際のユーザが使う状況と同じように機能するかを検証する重要なテスト手法です。FlutterやReact Nativeといったクロスプラットフォーム開発フレームワークを使用する際、特に重要になります。なぜなら、iOS、Androidなどの異なるプラットフォーム上でアプリが意図した通りに動作することを確認する必要があるためです。

E2Eテストには次のようなメリットがあります。
- **品質の向上**: アプリ全体の整合性を確認し、見落としやすい不具合を検出し、ユーザエクスペリエンス（UX）を向上させます。
- **自動化による効率化**: E2Eテストを自動化することで、リグレッションテストを効率的に実施し、人的ミスを減らすことができます。これにより、開発者がより複雑な機能の実装に集中できるようになります。

例えば、ログイン機能のE2Eテストでは、ユーザが正しいIDとパスワードを入力してログインし、その後期待する画面に遷移するかどうかを検証します。E2Eテストを自動化すると、毎回手動で操作を行う必要がなくなり、開発サイクルを加速させることにつながります。


## 1-2. Flutter と ReactNative の選定理由
Flutter と ReactNative を選定した理由は、開発効率の高さと優れた UX にあります。

Flutterは、Dart言語を使用した高性能なフレームワークです。すべてのUI要素がウィジェットで構成されているため、Material Design や Cupertino のデザインシステムを簡単に実装でき、一貫性のある美しいUIを構築できます。ホットリロード機能により、開発中の変更が即座に反映され、開発効率が大幅に向上します。

ReactNative は、JavaScript の知識を活かしてモバイルアプリを開発できるため、Web開発者にとっても学習コストが低く、既存のWeb開発リソースを最大限に活用できます。Reactのコンポーネントベースのアーキテクチャを継承しており、大規模なアプリケーションでも保守性が高く、再利用性の高いコードを記述できます。

今回の検証に際して、短期間で複数のプラットフォームに対応するアプリを開発して検証することが求められていました。Flutter と ReactNative はそれぞれ高速な開発、優れたパフォーマンス、大規模なコミュニティという強みを持っており、これらの要件を満たす最適な選択肢であると判断しました。特に、Flutter のホットリロード機能は、迅速なプロトタイピングと開発サイクルの短縮に大きく貢献し、ReactNative のJavaScriptエコシステムは、既存のWeb開発資産の活用を可能にしました。


# 2. Flutter でのE2Eテスト

## 2-1. integration_test と Maestro の採用
Flutter のE2Eテストに integration_test と Maestroを採用したのは、それぞれが提供する独自の強みとシームレスな連携によるものです。integration_test は、Flutter公式がサポートするライブラリで、アプリケーションが実際の端末やエミュレータ上でどのように動作するかを詳細に検証するために設計されています。このツールは、ユーザインターフェースのテストはもちろん、アプリケーション全体のパフォーマンスをリアルタイムで確認することができ、実際のデバイスでの動作確認に不可欠です。

一方、Maestro は、特にアプリケーションの状態管理と複雑なユーザインタラクションのテストに強みを持っています。このツールを使用することで、開発中のアプリケーションが予期しない状態変化やユーザアクションにどのように対応するかを効果的に検証し、よりリアルなユーザ操作をシミュレーションすることが可能です。

integration_test および Maestro により、シンプルなUIテストから複雑なビジネスロジックまで、幅広いテストニーズに対応し、全体的なアプリケーションの品質を向上させることができます。このような背景から、これらのツールはプロジェクトのテスト戦略において不可欠な選択となります。


## 2-1. integration_test
integration_test の大きなメリットは、アプリケーションが実際の端末やエミュレータ上でどのように動作するかを直接テストすることができる点にあります。ユーザ操作を模倣して実際の動作環境を再現するため、UIとビジネスロジックの両方を網羅的に検証できます。また、Flutter SDK との高い互換性と公式サポートにより、安定したテスト環境を構築できることも大きな利点です。さらに、CI/CDパイプラインとの統合が容易であり、テストの自動化と効率化が可能です。

しかしながら、デメリットとしては、テストのセットアップと実行が複雑になることが挙げられます。特に、大規模なアプリケーションや多くの機能を持つアプリでは、テストスクリプトが複雑になりがちで、その結果、テストの実行時間がかかることがあります。また、特定のデバイス固有の機能やOSに依存するテストは制限があるため、全てのシナリオをカバーすることが難しい場合があります。これにより、環境設定の複雑さが増すと、テストのメンテナンスが困難になることがあります。

総じて、integration_test は Flutter アプリケーションにおける品質保証に不可欠なツールであり、適切に管理された場合にはプロジェクトの信頼性を大幅に向上させることができます。採用にあたってはこれらのメリットとデメリットを理解し、プロジェクトのニーズに合わせて適切なテスト戦略を立てることが重要です。


## 2-2. Maestro
Maestro の主なメリットは、開発者が非常に詳細かつ具体的なテストシナリオを設計できる点にあります。ユーザイベントのシミュレーションやアプリケーションの各種動作をリアルタイムで確認することが可能で、これによりエンドユーザの体験に基づいたテストが行えます。MaestroはフレキシブルなAPIを提供しており、カスタムテスト要件に応じてツールをカスタマイズすることが可能です。この高度なカスタマイズ性は、特に複雑なアプリケーションや多層的な状態遷移を持つプロジェクトにおいて、予期せぬバグや不具合を早期に特定し、修正するために非常に有効です。

しかしながら、デメリットとしては、その高度な機能性が初学者には扱いにくく、学習曲線が急である点が挙げられます。進んだ使用法が求められ、特に複雑なテストシナリオのセットアップには詳細な計画と時間が必要になります。設定とメンテナンスに時間がかかる可能性があり、特に大規模なプロジェクトでは、テストプロセスの準備と実行に相応のリソースを投入する必要があります。このため、初期のセットアップが複雑であり、継続的なメンテナンスが必要となることも考慮する必要があります。

全体として、Maestro はその柔軟性と詳細なテスト能力によって、Flutterアプリケーションの品質保証において重要な役割を果たします。適切に管理された場合、開発チームはアプリがさまざまな問題に強くなるように大きく改善できますが、そのためにはツールの特性を十分に理解し、プロジェクトのニーズに合わせて適切なテスト戦略を立てることが重要です。


# 3. ReactNative でのE2Eテスト

## 3-1. Detox
Detox は、ReactNativeアプリケーションのE2Eテストに特化した強力なフレームワークであり、その採用は多くの利点を開発チームに提供します。

採用すべき理由として、Detox は非常に安定したテスト環境を提供し、テストにかかる時間と工数を大幅に削減することにつながります。これにより、開発者は信頼できるテスト結果を得ることが可能で、バグの早期発見と修正を行うことができます。また、Detox はシミュレータや実デバイス上でのテストを自動化し、実際のユーザ操作を正確に再現することができます。このツールは、継続的インテグレーション（CI）システムとの統合が容易であり、開発サイクルを通じて一貫したパフォーマンスを維持するのに役立ちます。

メリットとしては、セットアップが比較的簡単で、直感的なAPIが提供されているため、開発者が迅速にテストを構築できることが挙げられます。さらに、プログラム的にアプリケーションを制御する能力により、非同期処理やネットワーク要求の管理が容易となり、幅広いプラットフォームでの互換性も高いです。

しかし、デメリットとしては、特にiOS環境においてはXcodeやAppleのエコシステムに依存するため、設定や更新が煩雑になることがあります。また、AndroidのサポートがiOSに比べて劣る点や、詳細なドキュメンテーションの不足が指摘されることがあります。さらに、初期の学習曲線がやや高く、継続的なメンテナンスや高度なテストシナリオのセットアップには多くのリソースが必要です。


# 4. Flutter と ReactNative でのE2Eテストの比較
## 4-1. セットアップ
※導入手順の詳細は本記事の趣旨から外れるため割愛しています。

#### integration_test
integration_test の導入手順は非常に簡単です。
- pubspec.yaml に integration_test を追加してインストール
- テスト用ドライバーコード、テストコードを作成
- テストコマンドを実行してアプリ起動

#### Maestro
Maestro の導入手順も非常に簡単です。
- curl や brew コマンドを使用してインストール
- yamlファイルにテスト操作を記述
- Maestroを実行してアプリ起動

#### Detox
上記の２ツールより手順は多くなります。
- Detox-cli、jest、detox、macの場合はapplesimutilsというツールをインストール
- detox init を実行し初期化
- Detox設定ファイルにアプリ情報や使用するデバイス情報を記述
- detox build でビルドが通ることを確認
- テストコードを記述
- detox test を実行してテスト起動


## 4-2. テストコード
対象のアプリは簡単なTODO管理アプリです。
テストでは簡単に以下のようなことを確認します。
- アプリ起動
- ドロワー（メニュー）を開く
- 登録用モーダルを開く
- 登録内容を入力して保存する
- 一覧に保存した内容が表示される

#### integration_test
Dart言語そのままに、タップ操作なら tester.tap というように直感的にコードを記述することができます。 以下コードは登録用モーダルを開いてキャンセルで閉じる操作で、スクリーンショットを保存することも可能です。スクリーンショットがあることで、モーダルが正常に開いて閉じられたのか確認でき、エビデンスとして有効なため非常に有用です。
また、動作の所感としては、目視では追えないほどでしたので、非常に速いと感じました。
``` Dart
// 追加ボタンをタップ
await tester.tap(find.byIcon(Icons.add));
await tester.pumpAndSettle();
// スクリーンショットを撮る
await binding.takeScreenshot('modal_opened');
// キャンセルボタンをタップ
await tester.tap(find.text('キャンセル'));
await tester.pumpAndSettle();
// スクリーンショットを撮る
await binding.takeScreenshot('modal_closeed');
```

#### Maestro
yamlファイルですので、コードというより設定内容を列挙して作成するイメージです。yamlなど設定関連のファイルに見慣れていれば違和感なく記述できるでしょう。タップ操作が tapOn というイメージしやすい設定になりますので、操作方法は記述しやすく感じました。スクリーンショットが容易に保存できることもポイントです。
また、動作の所感としては、目視で追えないほどではなく、integration_test と比較すると若干遅い印象を受けました。しかし、もっさりとした遅い感触ではありませんので、許容範囲かと感じました。

``` yaml
# タスクモーダルを表示してキャンセル
- tapOn:
    text: "add_todo"  # 追加アイコンをタップ
- assertVisible:
    text: "タスク名" # 入力欄が表示されていること
- takeScreenshot: before_cancel_new_task  # 閉じる前のスクリーンショット
- tapOn:
    text: "キャンセル"  # キャンセルボタンをタップ
- takeScreenshot: after_cancel_new_task  # 閉じた後のスクリーンショット
```


#### Detox
一目瞭然だと思いますが、上記の integration_test や Maestro と比較してコード量は多いです。さらに、スクリーンショットが撮れない点がE2Eテストとして採用するかの観点では非常に痛いポイントです。 とはいえ、Javascript(Typescript)で記述できるため、ReactNativeでアプリを作成している以上は馴染みのある感覚で記述できるでしょう。
``` javascript
  // ＋をタップ
  it('should tap the add task button', async () => {
    await element(by.text('＋')).tap();
    await waitFor(element(by.text('期限')))
      .toBeVisible()
      .withTimeout(3000);
    await expect(element(by.text('期限'))).toBeVisible();
  });

  // モーダルのキャンセルをタップ
  it('should tap the cancel button in the modal', async () => {
    await element(by.text('＋')).tap();
    await waitFor(element(by.text('キャンセル')))
      .toBeVisible()
      .withTimeout(3000);
    await element(by.text('キャンセル')).tap();
    await waitFor(element(by.text('キャンセル')))
      .not.toBeVisible()
      .withTimeout(3000);
  });
```


## 4-3. 比較結果と導入提案
Flutter と ReactNative において、それぞれのツールが持つ独自の特性や強みを検証しました。
以下に、これまでの比較結果を踏まえた詳細と、導入時の提案をまとめます。

#### テストコードの比較
Flutter（integration_test と Maestro）
- コード量: より少なく、直感的な操作が可能
- 実行速度: 高速であり、開発サイクルを加速
- 操作の直感性: Dart言語を用いるため、Flutter開発者にとって自然な記述が可能
- スクリーンショットの取得: テスト中の画面状態を簡単に記録

ReactNative（Detox）
- コード量: 比較的多く、初期設定が複雑
- 実行速度: 標準的であり、安定性に重点
- 操作の直感性: JavaScriptまたはTypeScriptを使用するため、Web開発者に親しまれる
- スクリーンショットの取得: 取得が困難

#### 総合的な考察
Flutter と ReactNativeのテストツールは、それぞれに独自の利点があります。
重要な違いがあることを以下の比較表で提示します。

| 比較項目       | Flutter                          | ReactNative                    |
|----------------|----------------------------------|--------------------------------|
| **コード量**       | 少なく、シンプルな記述が可能       | 多く、やや複雑                 |
| **実行速度**       | 高速で反応が良く、効率的なテストが実現 | 標準的な速度                   |
| **CI/CDとの統合容易性**   | 簡単でスムーズに統合可能           | 設定が煩雑になる場合があり、やや手間 |
| **カスタマイズ柔軟性**        | Dart言語による柔軟なカスタマイズが可能 | JavaScriptの利点を活かした広範な拡張性 |
| **スクリーンショット** | 容易に取得可能                    | 取得が困難         |

これらの情報を踏まえると、プロジェクトのニーズに応じて適切なテストツールを選択することが重要です。Flutter は迅速な開発と簡単なCI/CD統合を重視する場合に適しており、ReactNative は既存のJavaScriptスキルセットを生かし、より詳細なテスト制御が必要な場合に適しています。



# 5. まとめ
本記事では、Flutter と ReactNative におけるE2Eテストの導入を比較しました。

Flutter は、integration_test と Maestro の適切な組み合わせにより、迅速な開発サイクルと簡潔なコードでのテストが可能であり、開発プロセスを大幅に加速させる利点があります。
一方、ReactNative の Detox は、より複雑な制御が可能で安定したテスト環境を提供し、特にJavaScriptが得意な開発チームには適しています。

プロジェクトの具体的な要件、開発チームの技術スタック、そしてプロジェクトの目標を考慮して、最適なテストツールを選択することが、高品質なアプリケーションを効率的に開発する鍵です。本記事において検証したテストツールはそれぞれ独自の強みを持っており、これらを理解し適切に活用することで、アプリケーションの品質を保証し、開発プロセスをスムーズに進めることが可能となります。


最後に、本記事がクロスプラットフォーム開発に携わるエンジニア皆様の気づきとなれば幸いです。