Flutterの自動テストを俯瞰する

ある程度自動テストの心得があるFlutter初心者の理解/解釈。

Flutterは割と自動テストしやすい流れが整っている。

開始点

公式のTesting Flutter appsが開始点としてよくまとまっている。

概要をつかむには「flutter テスト」でググって出てきた日本語のページを適当に読むのもいい。
ただし情報の陳腐化に注意。
特にサンプルコードを動かすならまずは公式のものから当たったほうがいい。

テストの種類

Flutterの自動テストには以下の3種類がある。

  • ユニットテスト
  • ウィジェットテスト
  • インテグレーションテスト

感触としてはウィジェットテストを主に書くことになりそう。

ユニットテスト

関数、メソッド、クラスの単体テスト。

ある程度複雑な処理を個別に切り出したものに対して書いていくかんじになるだろうか。

ウィジェットテスト

ウィジェットの単体テスト。
UIの表示や操作のテストで、タップやテキスト入力などの記述も可能。

ほぼインテグレーションテストと同じことができるのだが、個々のウィジェットにフォーカスしたテストを意識することで、シンプルでメンテしやすいテストをテンポよく書くことができる。
デバイスに依存しないためインテグレーションテストより高速に実行できるのもテストサイクルを回すうえではうれしい。

一方でウィジェットテストの環境は実際のデバイスと比べると簡易実装のためテストしきれない部分がある。例えばタイマーを使った処理では実際にはタイマーが動かない、SharedPreferenceが利用できないなど。
そうしたものはインテグレーションテストで対応するほかない。

インテグレーションテスト

AndoridエミュレーターやiOSシミュレーターといったデバイス上で動作する結合テスト。
実機で動かすのにより近い結果が得られ、アプリのパフォーマンスを測ることもできる。

実行に時間がかかるのが難。

テスト実行に必要なpubspec.yaml

ユニットテスト、ウィジェットテスト、インテグレーションテストの各ページで必要なpubspec.yamlの記述が紹介されているが、すべて利用するor後で利用するかもしれない、なのでまとめて以下とする。

dev_dependencies:
  flutter_test:
    sdk: flutter
  integration_test:
    sdk: flutter

ちなみにこれは公式のインテグレーションテストのドキュメント通りのもの。

改めて確認したらこの記事のドラフトを書いていた数日前にはなかったflutter_driverの読み込みがいつの間にか追加されているけど、オプショナルと書いてあるしなくても動いているので、今のところなしで行く。

ユニットテスト

An introduction to unit testingを読んでやって行く。

他の言語でユニットテストをかいたことがあるなら難しいことは特にない。

上記イントロダクションではpubspec.yamlでtestパッケージを読み込んでいるが、Flutterでしか使わないものを作っているならflutter_testでもいいというかそちらの方が便利。
Flutterに依存しないもの(DartのライブラリとしてFlutter外でも使えるもの)として作りたいならtestパッケージを使うといい。

testを使うときはテストコードでtestパッケージを読み込む。

import 'package:test/test.dart';

flutter_testを使うときは以下。

import 'package:flutter_test/flutter_test.dart';

ひとつのテストで両方読み込むことはできない。

実行方法

コンソールからの実行は以下のコマンドで。

flutter test
00:01 +2: All tests passed!

個々のファイルを指定して実行もできる。

flutter test test/counter_test.dart
00:01 +2: All tests passed!

VSCodeでFlutterの拡張を入れていればエディタからの実行も簡単で良い。

ウィジェットテスト

An introduction to widget testingを読んでやっていく。

Finder

画面上の要素を操作したり検証するためにFindしたいけどどうするのって言う向きに以下のページにメソッドが並んでいる。

Flutter flutter_test CommonFinders class

色々あるけど基本findしたい要素にはKeyをつけてfind.byKeyするのが手堅い模様。

MaterialApp

公式のイントロダクションのサンプルコードは以下のようになっているが。

await tester.pumpWidget(MyApp());

実践的にはウィジェットの単体テストなのでアプリ全体より対象のウィジェットだけを読み込む方がよいと思われる。

await tester.pumpWidget(MaterialApp(home: Counter(title: 'Counter')));

参考: 【Flutter】Widget テストの「あれ、これどうやるんだろう?」集

実行方法

ユニットテストと同様。flutter testで両方まとめて実行される。エディタから実行しても良い。

flutter test

インテグレーションテスト

まず昔からあるAn introduction to integration testingは古いやり方になっているので注意。
2021年7月現在、日本語の試した系記事はこちらのやり方になっていることが多い。

これでも動かないことはないが、今はIntegration testingの方が推奨されたやり方になっている。
Finderの書き方がウィジェットテストと共通になっていて助かる。

Finder

WidgetのFinderと共通。
古いサンプルコードにあるflutter_driverのものは使わなくてよくなっているので、byValueKeyが動かないぞ、といったハマり方をしないように。

Keyがないぞ?

試しにサンプルコードを拡張してfind.byKeyしようとするとKeyがないとエラーが出てしまう。
material.dartを読み込んでやりましょう。

import 'package:flutter/material.dart';

こいつが鍵。

実行方法

実行前にAndroidエミュレーターやiOSシミュレーターを立ち上げておく必要がある。

例によってエディターからの実行も手軽だがコマンドからなら以下の通り。

flutter drive --driver=test_driver/integration_test.dart --target=integration_test/home_test.dart

ウェブでの実行もできるはずなのだが、上記イントロダクションの Running in a browser のセクション通りにChromeDriverをポート4444で立ち上げて実行してみたりしたけどうまく行かなかった。
ハマりの気配がするので今のところ深追いはしないでおく。