Flutter-WidgetTestでちょっと調べたこと

Flutterのウィジェットテストを行うときにつまずいたことなど。

  • AppBarにあるポップアップメニューを表示する。
  • onSubmittedへのテキスト入力。
  • テスト用のファイルの位置。
  • テストで使用するファイル名の生成。
  • void asyncなメソッドへの対応。

AppBarにあるポップアップメニュー

await tester.tap(find.byType(PopupMenuButton<T>))

Tはメニューに設定されている型で、Stringなどなどで、上のTを変える。
一応これでポップアップメニューが表示される。

最初はメニューのアイコンをタップしなきゃダメかなと思ったていて、そのアイコンを探すのに手間どってしまっていた。

onSubmittedへのテキスト入力

入力フィールドへのテキスト入力はtester.enterTextを使うのだけど、onSubmittedを呼び出すためのテストドライバーはないようだ。

そのため、ウィジェットを検索して直接onSubmittedを呼び出すしか方法がない。

Flutter and Widget Tests - Flutter Institue
We're going to discuss the basics of widget testing using flutter.

テスト用のファイルの位置

テストプログラムの実行パスは基本プロジェクトルートになるので、そこからの相対パスで設定する。

test以下に配置するのであれば”test/~”といった感じ。

ただtestにはテストモジュールを配置するので、ルートに別フォルダを起こしたほうが良いのだろうか?
Stack Overflowではtest_resourcesというのに置いておくみたいな書き方をしていたのがあった。

Attention Required! | Cloudflare

私もそれに合わせてみた。

テストで使用するファイル名の生成

sqfliteで使用するSQLデータベースファイルを、個々のテストで独立させたかったので、テスト単位にユニークなファイル名を作成しようと思ったのが始まり。

  1. そこで思い立ったのが、グローバル変数でカウント値を保持し、”filename${count++}.db”の様な形でファイル名を生成しようとした。
    これでいけると思い、クラスstaticを保持しこのカウントアップで対応しようと思い設計をして、大体できた時にテストを実行してみた。しかしこれは失敗。グローバル変数(もしくはクラスのstatic変数)のライフは、基本的に~_test.dartの中のみで、別の~_test.dartでは0初期化されてしまった。
    そのため、テスト中で同一ファイル名が生成されてしまい、他のプロセスで使用されていますというエラーメッセージが出て失敗してしまった。
  2. ついで考えたのは、テストのデスクリプションをもじったもの。
    これはtest(“description”, () {})のdescriptionを取り出したもの。これも、異なるクラスで同一名のメソッドの場合には同一名を付けてしまっていたので、ユニークにならずに失敗。
  3. 最後に、ファイル名(~_test.dart)を取り出せないかを検討し、これはできたのだけど上位側からオブジェクトを渡さなければいけなかったので、既に作成したコードの変更量が大きかったので断念。

結果、1で作成したクラスにカウンターの初期値をmain関数最初で決定して使うようにした。

import 'dart:io';

import 'package:path/path.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';

class DbMock extends DbHelper {
  static Future<void> removeDbFile(String name) async {
    final file = File(join(await databaseFactoryFfi.getDatabasesPath(), name));
    if (await file.exists()) {
      await file.delete();
    }
  }

  static int counter = 0;

  static void setCounter(int value) {
    counter = value;
  }

  /// クローズ処理
  @override
  Future<void> close() async {}

  DbMock(String dbName) : super(dbName);

  static Future<DbMock> create() async {
    final dbfile = "test${counter++}.db";
    await removeDbFile(dbfile);
    return (await DbMock(dbfile).init()) as DbMock;
  }
}

void asyncなメソッドへの対応

FutureであればawaitすればいいのだけどFutureが付かないとawaitで同期させることができないので、別の手段が必要。

今のところ、await Future.delayed(const Duration(seconds: 0));で調整をするとうまくいった。
失敗した場合、適時0をほかの値に変更する。

自分が作成した関数群とその呼び出しならFutureOr<void>としておき、テスト時はawaitするというのもありかもしれない。
(しかし、実際のテストで動きがおかしいとなるかもしれないので、設計として不味いのかもしれない)

テストではないけど、実装としては以下の様な感じのもの。

void test() async {
  await Future.delayed(Duration(seconds: 1));
  for (int i = 0; i < 1000; i++) {
    print(i.toString());
  }
}

void main(List<String> arguments) async {
  test();
  await Future.delayed(Duration(seconds: 0));
  print("end");
}

コメント

タイトルとURLをコピーしました