Flutter

【Flutter】Widgetテストのシンプルな書き方

Flutterでアプリを作成する際に自動テストをしっかりと書くようにしています。

Flutterによる自動テストを3記事に分けて公開するうちの2記事目です。

本記事ではWidgetテストについての備忘を書いていきます。

1記事目「【Flutter】Riverpod使用時の単純なユニットテストの書き方」はこちらからご確認ください。

【Flutter】Riverpod使用時の単純なユニットテストの書き方最近、Flutterでアプリを作成する際にテストをしっかりと書くようにしています。 Riverpodを使用するときのユニットテスト...

3記事目「【Flutter】Integrationテストのシンプルな書き方」はこちらからご確認ください。

【Flutter】Integrationテストのシンプルな書き方Flutterでアプリを作成する際に自動テストをしっかりと書くようにしています。 Flutterによる自動テストを3記事に分けて公...

FlutterにおけるWidgetテストとは

FlutterにおけるWidgetテストとは、割とそのままですがWidgetの表示や動きが正しいかどうかをテストしていきます。

ボタンであれば、ボタンのテキストの確認であったり、ボタンをタップしたときの動作を確認するようなテストを書いていきます。

Widgetテストをしっかりと書くことによって、見た目の部分についてテストをすることができます。

ユニットテストではロジックの動きをテストしましたが、Widgetテストでは動き+見た目のテストをすることができます。

アプリの基本的な動きをテストすることでスモークテストのような感じにするのも良い感じです。

WidgetテストするViewクラス

前回のユニットテスト編でテストしてたロジッククラスを組み込んだ、ボタンをテストしていきます。

ソースコードは以下のとおりです。

全体像

class MyHomePage extends HookWidget {

  Widget build(BuildContext context) {
    final text = useProvider(calcProvider.select((value) => value.text));
    return Scaffold(
      backgroundColor: colorMain,
      body: Container(
        margin: const EdgeInsets.only(bottom: 20,),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.end,
          children: <Widget>[
            Row(
              children: <Widget>[
                Expanded(
                  child: Text(
                    text,
                    style: const TextStyle(
                      color: colorText,
                      fontSize: 60,
                    ),
                    textAlign: TextAlign.right,
                    maxLines: 1,
                  ),
                )
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Expanded(child: Button(text: "C", colorButton: colorFunc, colorText: colorMain)),
                Expanded(child: Button(text: "+/-", colorButton: colorFunc, colorText: colorMain)),
                Expanded(child: Button(text: "%", colorButton: colorFunc, colorText: colorMain)),
                Expanded(child: Button(text: "/", colorButton: colorCalc, colorText: colorText)),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Expanded(child: Button(text: "7", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "8", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "9", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "x", colorButton: colorCalc, colorText: colorText)),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Expanded(child: Button(text: "4", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "5", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "6", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "-", colorButton: colorCalc, colorText: colorText)),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Expanded(child: Button(text: "1", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "2", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "3", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "+", colorButton: colorCalc, colorText: colorText)),
              ],
            ),
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              children: <Widget>[
                Button(text: "0", colorButton: colorNum, colorText: colorText),
                Expanded(child: Button(text: ".", colorButton: colorNum, colorText: colorText)),
                Expanded(child: Button(text: "=", colorButton: colorCalc, colorText: colorText)),
              ],
            ),
          ],
        ),
      ),
    );
  } // end of state class

}

ボタンWidget

class Button extends HookWidget {
  final String text;
  final Color colorButton;
  final Color colorText;

  const Button({
    Key? key,
    required this.text,
    required this.colorButton,
    required this.colorText,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    final controller = useProvider(calcProvider.notifier);
    return Container(
      padding: const EdgeInsets.symmetric(vertical: 5),
      child: ElevatedButton(
        child: Padding(
          padding: text == "0"
              ? const EdgeInsets.only(left: 20, top: 20, right: 120, bottom: 20)
              : text.length == 1
                  ? const EdgeInsets.all(22)
                  : const EdgeInsets.symmetric(horizontal: 15, vertical: 22),
          child: mapIcon.containsKey(text)
              ? Icon(
                  mapIcon[text],
                  size: 30,
                )
              : Text(
                  text,
                  style: const TextStyle(fontSize: 30),
                ),
        ),
        onPressed: () {
          controller.input(text);
        },
        style: ElevatedButton.styleFrom(
          primary: colorButton,
          onPrimary: colorText,
          shape: text == "0" ? const StadiumBorder() : const CircleBorder(),
        ),
      ),
    );
  }
}

FlutterによるWidgetテストの書き方

早速Widgetテストを書いていきます。

void main() {
  group("Button Single Test", () {
    testWidgets("Button Single Test", (WidgetTester tester) async {
      await tester.pumpWidget(
        ProviderScope(
          child: MaterialApp(
            home: Button(
              text: "0",
              colorButton: Colors.white,
              colorText: Colors.black,
            ),
          ),
        ),
      );

      await tester.pump();
      expect(find.text("0"), findsOneWidget);
      expect(find.text("2"), findsNothing);
    });
  });

  group("Tap Button Test", () {
    testWidgets("Tap Button Test", (WidgetTester tester) async {

      await tester.pumpWidget(
        ProviderScope(
          child: MyApp(),
        ),
      );

      await tester.pumpAndSettle();
      expect(find.text("0"), findsNWidgets(2));

      await tester.tap(find.text("1"));
      await tester.pump();
      expect(find.text("0"), findsOneWidget);
      expect(find.text("1"), findsNWidgets(2));

      await tester.tap(find.text("2"));
      await tester.pump();
      expect(find.text("12"), findsOneWidget);

    });
  });
}

 

Widgetテストでは「testWidgets」を使用してテストしていきます。

WidgetTesterのpumpWidgetで、テストする仮想のWidgetを準備します。

await tester.pumpWidget(
  ProviderScope(
    child: MaterialApp(
      home: Button(
        text: "0",
        colorButton: Colors.white,
        colorText: Colors.black,
      ),
    ),
  ),
);

 

準備ができたら早速検証していきます。

以下の1つ目のコードでは、Finderクラスを使用することでボタンのテキストに”0″が設定しているものを探し、1個見つかること検証しています。仮想のWidgetを準備したときに”0″を設定したWidgetを準備しているので、1個見つかりテストを通過することができます。

2つ目のコードでは、”2″が設定されているWidgetを探し、1つも見つからないことを検証します。

3つ目のコードのように書くと、複数存在することを検証できます。その場合は存在する件数を指定する必要があります。

expect(find.text("0"), findsOneWidget);
expect(find.text("2"), findsNothing);
expect(find.text("1"), findsNWidgets(2));

 

また、以下のように書くとWidgetをタップすることができます。

await tester.tap(find.text("2"));

このソースではタップすると一部のテキストが変わるので、変わった結果を検証することもできます。

このように、Widgetテストをすることで見た目や動きをテストしていきます。

まとめ

FlutterによるWidgetテストコードの書き方の備忘録でした。

書くのは少し大変そうですが、一度書いてしまえば以降は自動でテストできるようになるのでかなり効率化できそうです。

実際に画面をポチポチするのも大切ですが、自動テストできるようにすればデグレなども気付きやすくなり、品質の向上にも役立ちそうです。

是非、品質の向上のためにもWidgetテストを書いてみてください。

Flutter学習情報

Flutterを勉強するのに最適な参考書は、以下の「基礎から学ぶFlutter」です。

環境構築から、Dart/Flutterの基本/テストやパフォーマンスチューニングまで一通り学ぶことができます。

また、当ブログではFlutterを初心者が学ぶためにオススメな方法を「Flutter を初心者が学ぶおすすめの勉強法!【間違いない動画があります】」という記事で公開していますので、Flutterに興味がある方は是非読んでみてください。

Flutterに入門するためのオススメ勉強法【間違いない動画があります】Flutter 初心者が勉強する場合に、最高の動画をお伝えします。...

動画学習がしたい!という方にオススメの記事がこちらです。

【Udemy】スマホアプリ作成を学ぶときに受講しておきたい講座3選【Flutter】スマホアプリを初めて作成する場合、何らかの教材を見ながら進めると思います。 私は動画で学習するのが好きで、非常に効率よく学ぶことが...

Flutterの将来性はどうなのか?をまとめた記事はこちらです。

【2021/8 更新】Flutterの将来性をトレンドやGoogleの情勢から分析した結果クロスプラットフォーム開発のフレームワークの中で近年注目されているのが「Flutter」です。 私も2020年の4月頃から注目して...