🔍

Widgetテストによって発見できること

2024/12/06に公開

Widgetテストの意義

Widgetテストを行うことで、アプリのUIやロジックの不具合を早期に発見できる。特に、UIコンポーネントの動作やユーザー操作に関連する問題を検出するのに役立つ。この記事ではWidgetテストによって発見できる具体的な問題を挙げて扱う。
Widgetテストによって発見できる問題は大まかにいうと

  • ユーザーの操作に関連する動作不良(タップ、入力、スクロールなど)。
  • UI表示や非表示の問題。

具体例

1. ユーザー操作に伴うUIの動作不具合

  • ボタンが正しく反応しない。
  • ユーザーの操作(タップ、スワイプなど)に対して期待通りの画面変更が行われない。
    問題: ボタンを押しても次の画面に遷移しない。
testWidgets('ボタンを押すと正しく画面遷移が行われる', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  // ボタンをタップ
  await tester.tap(find.text('次へ'));
  await tester.pumpAndSettle();

  // 次の画面が表示されていることを確認
  expect(find.text('新しい画面の文字'), findsOneWidget);
});

2. UIコンポーネントの表示・非表示の問題

  • 必要なWidgetが表示されていない、または表示されるべきでないWidgetが表示されている。
  • 状態の変化に応じたWidgetの切り替えが正しく行われていない。
testWidgets('アプリ起動時のローディング中にIndicatorが表示され、ローディングが終わると消える', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  // ローディング中はIndicatorが表示される
  expect(find.byType(CircularProgressIndicator), findsOneWidget);

  // データ取得後、Indicatorが消える
  await tester.pumpAndSettle();
  expect(find.byType(CircularProgressIndicator), findsNothing);
});

3. ユーザー入力のバリデーションエラー

  • 不正な入力をした際に適切なエラーメッセージが表示されない。
  • 入力データが正しく処理されない。
testWidgets('空の入力フィールドでエラーメッセージが表示される', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  // フォームの送信ボタンをタップ
  await tester.tap(find.text('送信'));
  await tester.pump();

  // エラーメッセージが表示されることを確認
  expect(find.text('このフィールドは必須です'), findsOneWidget);
});

4. リストやスクロールの動作不具合

  • リストが正しくレンダリングされない。
  • スクロールが正しく動作しない。
testWidgets('リストの全ての項目が表示される', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  // リストが表示されていることを確認
  expect(find.byType(ListTile), findsNWidgets(10));

  // スクロールして新しい項目を確認
  await tester.drag(find.byType(ListView), const Offset(0, -300));
  await tester.pump();

  expect(find.text('アイテム11'), findsOneWidget);
});

5. 画面遷移の不具合

  • 正しい条件で画面遷移が行われない。
  • 遷移先の画面で状態が引き継がれない。
testWidgets('選択した項目が詳細画面に渡される', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());

  // 項目をタップ
  await tester.tap(find.text('アイテム1'));
  await tester.pumpAndSettle();

  // 詳細画面で正しいデータが表示されていることを確認
  expect(find.text('詳細: アイテム1'), findsOneWidget);
});

6. 条件分岐の確認

  • 条件に応じたUIが正しく表示されない。
  • 状態管理が正しく行われていない。
testWidgets('ログイン状態に応じて画面が切り替わる', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp(isLoggedIn: true));

  // ログイン状態ではダッシュボードが表示される
  expect(find.text('ダッシュボード'), findsOneWidget);

  await tester.pumpWidget(MyApp(isLoggedIn: false));

  // 非ログイン状態ではログイン画面が表示される
  expect(find.text('ログイン画面'), findsOneWidget);
});

7. UI表示の不整合

  • 日付や通貨のデータが正しい形式で表示されない。
  • 表示が不完全、欠落している。
testWidgets('データがnullの場合、デフォルト値が表示される', (WidgetTester tester) async {
  await tester.pumpWidget(
    MaterialApp(
      home: MyWidget(
        data: {
          'name': null, // 名前がnull
        },
      ),
    ),
  );

  // デフォルト値が表示されることを確認
  expect(find.text('Guest'), findsOneWidget);
});

8. オリジナルのWidgetの再利用による問題

  • カスタムWidgetの動作が期待通りでない。
  • 他の画面で再利用した場合に不具合が発生する。
testWidgets('カスタムボタンが期待通りに動作する', (WidgetTester tester) async {
    // モック関数を定義
    bool isPressed = false;
    void onPressedMock() {
      isPressed = true;
    }

    // テスト用ウィジェットを作成
    await tester.pumpWidget(
      MaterialApp(
        home: CustomButton(
          label: 'クリック',
          onPressed: onPressedMock,
        ),
      ),
    );

    // ボタンのラベルを確認
    expect(find.text('クリック'), findsOneWidget);

    // ボタンをタップして動作を確認
    await tester.tap(find.text('クリック'));
    await tester.pump(); // 状態更新を反映

    // ボタンの動作が正しいか確認
    expect(isPressed, true); // モック関数が呼ばれたことを確認
  });

まとめ

Widgetテストを行うことで、アプリのUIやユーザー操作に関する不具合を早期に発見・修正でき、アプリの品質向上と開発効率の向上につながる。

Discussion