📝

Flutter開発中でのSQFLite(ローカルDB)のテスト方法

に公開

私は、Flutterにおけるローカルデータの保存には、SQFLiteパッケージを使用してデータベース操作を行う場合が殆どです。
https://pub.dev/packages/sqflite
クエリの実行内容や、トランザクション処理に変更を加えた時などに、いちいち実機を立ち上げてテストするのは非常に面倒ですので、一括でテストが回せたら非常に便利です。

私の備忘録として残しておきます。

サンプルコード

テストには、sqfliteに加えて以下のパッケージが必要ですので、インストールしてください。

https://pub.dev/packages/sqflite_common_ffi

sqflite_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';

void main() {
  late Database db;
//テストが始まる前に一度だけ実行される
  setUpAll(() {
    TestWidgetsFlutterBinding.ensureInitialized();
    sqfliteFfiInit();
    databaseFactory = databaseFactoryFfi;
  });
//各テストが開始するたびに実行される
  setUp(() async {
    db = await CreateDatabase().getDatabase();
  });

//各テストが終わるたびに実行される
  tearDown(() async {
    //テストが終わるたびにテスト用DBを削除。
    await deleteDatabase(db.path);
  });

  test('Sqflite test', () async {
    //ここにDBのテスト内容を書く
  });
}

注目すべきはsetUp()とtearDown()の記述です。
https://api.flutter.dev/flutter/flutter_test/setUp.html
https://api.flutter.dev/flutter/flutter_test/tearDown.html

setUp()はテストが開始するたびに、tearDownはテストが終了するたびに実行されます。
このような書き方をすることで、どのテストにおいても独立したデータベースを使用することができ、他のテストへ影響を及ぼさないことに加え、実行順序にも依存しない関係ができるはずです。

Kent Beck氏は著書『テスト駆動開発』にて、次のように述べています。

テストの実行は、他のテストにどのような影響を及ぼすべきか---
決して及ぼすべきではない
『テスト駆動開発』、Kent Beck著; 和田卓人訳 p.189

Debugモードのテストだとデッドロックの可能性がある

もし、DebugモードでDBのテストを実施し、途中でエラーが出てしまう場合(トランザクション内でのエラー)テストが一向に終了せず、無限にグルグルしてしまいます。
こうなってしまうと、次に実行するテスタがDBにアクセスしようとしてデッドロックを引き起こしてしまいます。
なのでタスクマネージャーからキルしてしまいましょう。

ちなみに、エディタソフト(私の場合はVScode)を終了してもタスクマネージャにはプロセスが実行中として表示されていたため、やはりタスクキルするのがいいかと思います。

そもそもデバッグモードではなく、普通にrunさせればこのような問題は起こらず、トランザクションに失敗したらテストも終了します。

ただ、ブレークポイントを使って逐一変数を確認したい場合などは、debugモードを使いたいと思いますので、その場合はこの現象に注意してください。

まとめ

ある程度開発が進んだ後のクエリ編集やDBテーブル変更、制約の追加等は、腰が重くなってしまいがちですが、そうなってしまう理由はテストのしにくさにあると思っています。
DBのCRUD操作を簡単にテストできれば、かなり開発がしやすくなると思います。

Discussion