開発Tips

Flutter のテストカバレッジを 80% に上げる — 計測の落とし穴と実践パターン

「ぽんっ!」(旧称:でいりんぐ)のテストカバレッジを 60% 台から 80% に引き上げました。数字を追う前に計測そのものの落とし穴でつまずいたので、そこと実践パターンを残します。

背景(当時の状況)

カバレッジ基準を 80% に定めていたものの、実測は 60% 台にとどまっていました(2026-07-11 時点)。ところが数字を上げにかかる前に、そもそも「測り方」で数字が過小に出ていることに気づきます。まず計測の落とし穴を潰してからで 80% を超えるところまで持っていきました。

落とし穴①: 生成物を分母に入れると過小表示される

Flutter/Dart は生成コードが多いプロジェクトです。これらをカバレッジの分母に含めると、実力より大幅に低く出ます

  • app_localizations*.dart(多言語)、*.g.dart*.freezed.dart*.gr.dart などの生成物は評価から除外する(lcov.infoSF:(ソースファイル)行を見て、生成物のパスを手元で除外して集計する)。
  • 「ぽんっ!」では、生成物込みだと 40% 台に見えていたものが、除外すると実カバレッジになりました。

「数字が低い」の前に、何を分母にしているかを疑うべきでした。

落とし穴②: fresh worktree での計測は不正確

新しく git worktree を切った直後などに flutter test --coverage を回すと、lcov が不完全になることがあります。widget/screenshot テストが多くの lib ファイルを読み込まず、総行数が実際の 1/5 程度しか出ず、見かけ上のカバレッジが高く(あるいは低く)誤表示されました。

  • 依存や生成物が揃った主リポジトリで計測する。
  • worktree では、まず build_runner で生成物を作ってから測る。

実践パターン

数字を上げるうえで効いた具体策です。

リポジトリ層は fake を注入する

// FakeFirebaseFirestore をコンストラクタ注入してユニットテスト
final repo = FirestoreDailyCardRepository(FakeFirebaseFirestore());

FirebaseAuth は手頃な mock パッケージが無いので、mocktail で Mock implements FirebaseAuth を作って注入します。

autoDispose プロバイダは listen で保持してから await

// .future を直接 read すると「disposed during loading」で hang する
container.listen(myProvider, (_, __) {}); // 先に保持
final value = await container.read(myProvider.future);

無限アニメーション画面では pumpAndSettle を使わない

CircularProgressIndicator のように終わらないアニメーションがある画面で pumpAndSettle() を呼ぶとタイムアウトします。時間を指定した pump に置き換えます。

await tester.pump(const Duration(milliseconds: 300));

日付整形テストは初期化を await する

setUpAll(() async {
  await initializeDateFormatting('ja'); // await しないと raw フォールバックする
});

テストしにくい所は integration test に寄せる

すべてを unit/widget で賄う必要はありません。カメラプレビュー依存の画面や、ネイティブライブラリ依存のリポジトリ(ObjectBox など)は注入点が無く、無理に unit で書くより integration test で担保するほうが素直でした。カバレッジ目標は「全ファイル一律」ではなく、テスト種別ごとの適材適所で達成します。

まとめ

  • カバレッジは測り方で数字が変わる生成物を分母から除外し、依存の揃った主リポで計測する。
  • 数字を上げる本体は fake/mock 注入。autoDispose は listen で保持、無限アニメ画面は時間指定 pump、日付整形は初期化 await
  • テストしにくい箇所は integration test に寄せる。unit/widget/integration の役割分担でカバレッジ目標に届かせるのが現実的でした。