FlutterのRobotパターンUIテストライブラリーの解説

2023/12/23に公開

YUMEMI Flutter Advent Calendar 2023の23日目の記事です。

Robotパターンはソフトウェアテストの手法の一つで、ユーザーのアクションを一連の手順としてカプセル化し、それを再利用可能なクラスとして定義します。これにより、アプリケーションとのユーザーインタラクションを効率的にシミュレートし、テストすることが可能になります。これはアプリケーションの品質を向上させるのに役立ちます。

この記事では、Robotパターンと私が開発したRobotライブラリーについて紹介します。このライブラリーは、RobotパターンをFlutterで利用するためのもので、各ウィジェットのテストを行うためのRobotクラスを作成することができます。Robotライブラリーを使用することで、テストの再利用性と効率性を向上させ、結果的にアプリケーションの品質を向上させることができます。

Robotパターン

Robotパターンは以下の特徴があります。

  1. ユーザーのアクションを一連の手順としてカプセル化する
  2. それを再利用可能なクラスとして定義する
  3. アプリケーションとのユーザーインタラクションを効率的にシミュレートし、テストする

FlutterのWidgetテストやIntegrationテストでは、WidgetTesterやfindなどのAPIを使用してウィジェットをテストします。しかし、これらのAPIはテストコードを複雑にし、テストの再利用性と効率性を低下させる可能性があります。例えば以下のように、テストコードが複雑になりがちです。

// Robotを利用しない場合のテスト
testWidget('my test', (tester) async {
  await tester.pumpWidget(MaterialApp(home: MyWidget()));
  await tester.tap(find.byKey(const Key('my_widget_key')));
  await tester.pumpAndSettle();
  expect(find.text('Hello, World!'), findsOneWidget);
});

これはRobotパターンを使用していないテストの例です。まず、pumpWidgetメソッドを使用してMyWidgetを表示します。次に、特定のキーを持つウィジェットをタップします。その後、pumpAndSettleメソッドを使用してウィジェットの状態が安定するまで待ちます。最後に、expectメソッドを使用して特定のテキストが表示されていることを確認します。

pumpWidgetはMyWidgetを表示するためにMaterialAppやMaterial、Scaffoldを必要とするかもしれません。また、RiverpodのコードもMyWidgetを表示するために必要かもしれません。MyWidgetの状態の設定も必要とするでしょう。pumpAndSettleはMyWidgetの状態が変わるまで待つのですが、テストの本質ではありません。私たちがテストしたいことは以下のことだと考えられます。

  • MyWidgetが表示されているとき
  • ボタンをタップすると
  • Hello, World!と表示される

また、tapやexpectにおいて指定したキーやテキストを持つWidgetを見つけて利用していますが、可読性や再利用が低くなっています。

Robotパターンを利用すると、テストコードを以下のようにシンプルに書くことができます。

testWidget('my test', (tester) async {
  final robot = MyWidgetRobot(tester);
  await robot.show();
  await robot.tapHelloWorldButton();
  robot.expectHelloWorldAppears();
});

このテストコードでは、MyWidgetRobotクラスを使用しています。このクラスには、MyWidgetの表示、ボタンのタップ、Hello, World!の表示を確認するためのメソッドが定義されています。Robotを利用することで、テストの目的(What)を明確にし、テストの方法(How)を隠蔽することができます。これにより、テストコードが読みやすく、再利用しやすくなります。

Robotライブラリー

Robotライブラリーは、FlutterでRobotパターンを利用するためのライブラリーです。Robotライブラリーは、各ウィジェットのテストを行うためのRobotクラスを提供します。テストのHowを記述するクラスを作成しやすくします。

インストールとセットアップ

Robotライブラリーのインストールとセットアップは非常に簡単です。以下に手順を示します:

  1. まず、pubspec.yamlファイルにrobotパッケージを追加します。
dev_dependencies:
  robot: ^0.0.4
  1. 次に、プロジェクトのルートでflutter pub getを実行してパッケージをインストールします。

  2. 最後に、テストコードにrobotパッケージをインポートします。

import 'package:robot/robot.dart';

これで、Robotライブラリーの基本的なセットアップは完了です。次に、具体的な使用方法について説明します。

基本的な使用方法

Robotライブラリーを使用してテストを書く基本的な手順を以下に示します:

  1. まず、テストしたい各ウィジェットに対してRobotクラスをextendsしたクラスを作成します。
class MyWidgetRobot extends Robot<MyWidget> {
  MyWidgetRobot(super.tester);

  late String text;

  Finder get textFinder => find.descendant(of: this, matching: find.text(text));

  Future<void> show() => tester.pumpWidget(MaterialApp(home: MyWidget(text: text)));

  void expectText() => expect(textFinder, findsOneWidget);
}

RobotはFinderのサブクラスであり、MyWidgetRobotもFinderとして利用することができます。ですのでMyWidgetRobot内でexpect(this, findsOneWidget)とするとMyWidgetが存在するかを検証することができます。find.descendantのofにthisを渡せているのもそのためです。

textフィールドはMyWidgetに渡すデータです。

textFinderやshow、expectTextはHowを隠蔽してWhatをインターフェースとして提供しています。

  1. 次に、テストファイルを作成します。テストファイルでは、ウィジェットのRobotクラスとFlutterのテストパッケージをインポートします。
import 'package:flutter_test/flutter_test.dart';
import 'package:robot/robot.dart';

import 'my_widget_robot.dart';

void main() {
  group('MyWidget', () {
    testWidgets('should show text', (tester) async {
      final robot = MyWidgetRobot(tester)..text = 'Hello, World!';
      await robot.show();
      robot.expectText();
    });
  });
}

以上が基本的な手順となります。Robotクラスを拡張することで、特定のウィジェットに対するテストを独自に定義することができます。また、Robotクラスは拡張可能であり、特定のテスト要件に合わせてカスタマイズすることができます。

次に、より高度な使用例やテクニックを紹介します。

高度な使用例とテクニック

Robotライブラリーは、基本的な使用方法だけでなく、より高度なテストシナリオにも対応しています。以下に、いくつかの高度な使用例とテクニックを紹介します。

  • 特定のキーを持つウィジェットをテストするために、Robot.byKeyコンストラクタを使用することができます。
  • Robotクラスをそのまま利用することができます。
final robot = Robot<Text>.byKey(tester, const Key('count'));
robot.expectText('1');

Robotをそのまま利用してKey('count')に対するTextのRobotを作成し、expectTextで表示を確認することができます。独自WidgetのRobot<CustomWidget>のインスタンスを作成することも可能です。それらにexpectなどを作成するにはextension on Robot<CustomWidget>で行うことができます。

  • ウィジェットの表示を確認:ウィジェットの表示を確認するために、expectShown/expectHiddenメソッドを使用することができます。
robot.expectShown();
robot.expectHidden();

上記以外にもいくつかのテストをサポートするメソッドが実装されています。今後も増加させていく予定ですが、もし搭載してほしいメソッドがあればIssueやPull Requestをお待ちしています。(日本語で記述していただいて大丈夫です)

これらの高度な使用例とテクニックを利用することで、より複雑なテストシナリオも効率的に実装することが可能となります。詳細な使用方法や各メソッドの詳細については、robotパッケージのドキュメントを参照してください。

アドバイス

  • Robotパターンは細かく記述するとRobotのメンテナンスが大変になる可能性があります。適切な粒度でRobotを作成することが重要です。
  • Robotの内部で分岐を利用してHowを記述することは避けるべきです。もし分岐が必要になった場合は実装やテストを再検討することをおすすめします。
  • Robotの内部でRobotの状態を持つことは避けるべきです。状態を持つことでテストの再利用性が低下する可能性があります。

この記事では、RobotパターンとRobotライブラリーの詳細な使用方法を説明しました。Robotパターンを利用することで、テストの再利用性と効率性を向上させ、アプリケーションの品質を向上させることができます。Robotライブラリーは、このパターンをFlutterで簡単に利用できるように設計されています。その結果、テストコードが読みやすく、再利用しやすくなります。

今後もRobotライブラリーの機能を拡張し、より多くのテストシナリオをサポートする予定です。ご意見やご要望がありましたら、ぜひお知らせください。このライブラリーが、多くのプロジェクトのテストの改善に貢献できれば嬉しいです。

合同会社CAPH TECH

Discussion