生成AIで始めるFlutterユニットテスト導入 - Cursor agentを利用して
Flutterアプリの品質を高めるうえで、ユニットテストは欠かせません。特にビジネスロジック(ドメインロジック)が多い場合は、早い段階でユニットテストを導入しておくと後々の開発が安定します。
本記事では、MiniCartというミニECアプリを例に、生成AIを活用してドメインロジックのユニットテストを作成・実行するまでの流れを紹介します。
最後まで読むと、Flutterアプリでユニットテストを手早く導入し、生成AIを活用する具体的な手順とコツが身につきます。
なぜFlutterアプリにユニットテストが必要か?
ユニットテストのメリット
-
ドメインロジックの信頼性向上
プログラムの最小単位(関数やクラス)が意図どおり動作することを確認できます。ビジネスルールが複雑になるほど、テストの重要度は高まります。 -
保守性の向上
コードが大きくなってもテストが通るかぎり、変更の影響範囲を把握しやすいです。仕様変更後、テストが壊れた箇所を起点に修正すればよいので、開発スピードが落ちにくくなります。 -
リファクタリングのしやすさ
「テストがあるからこそ、大胆にコードを改善できる」というメリットがあります。
本記事では扱いませんが、後日のリファクタリング時にも大いに役立ちます。
Flutterアプリでドメインロジックが複雑化しがちな理由
FlutterはUI構築に優れていますが、UIの背後にはしばしば複雑なビジネスロジックが存在します。
アプリ内で以下のような機能を扱う場合、ビジネスルールの整合性を保つためにユニットテストが重要です。
- 顧客データ管理
- カート内アイテムの計算
- 在庫チェックやクーポン適用
MiniCartデモプロジェクトの概要
本記事では、最小限のECアプリ「MiniCart」を想定し、以下の機能を備えています。
-
商品一覧画面
- 商品名と価格を表示し、タップで詳細ページへ遷移。
-
商品詳細画面
- 選択した商品の説明を確認。
- 「カートに追加」ボタンでカートにアイテムを追加。
-
カート画面
- カートに入っている商品を一覧表示(商品名 x 数量、合計金額)。
- 割引クーポンコードを入力すると割引率が適用される(例:
DISCOUNT10
で 10%オフ)。
3層構造を基本にしています。
-
Domain層 (ドメインロジック)
- 例:
Product
などのモデル、CartService
などのビジネスロジッククラス
- 例:
-
Application層 (状態管理)
- 例:
CartNotifier
など。UIからの操作を受け取り、Domain層に処理を委譲
- 例:
-
UI層
- 例:
ProductListPage
,CartPage
などのFlutterウィジェット類
- 例:
今回の例では、Domain層のテストに絞って説明します。
生成AIを使ったテストコード作成の流れ
以下のステップでテストコードを作成します。
-
テスト対象のクラスを用意
- まずはテスト対象となるクラスやメソッド(例:
CartService
)が存在していることが前提です。
(TDDだとテストが先の場合もありますが、ここでは実装済みの想定です)
- まずはテスト対象となるクラスやメソッド(例:
-
テスト方針・ルールを作成
- 生成AIが参考にしやすいように、テストの基本方針や命名規則などをまとめたルールを用意します。
-
AIにコードを見せて指示を出す
- テストしたいクラスのソースコードをAIに提示し、たとえば「カートに商品を追加する機能をテストしたい」「クーポン割引を検証したい」といった具体的な指示を与えます。
-
AIが生成したテストコードをレビュー・修正
- AIが生成したコードをそのまま使えるとは限りません。インポートの調整や命名、テストケースの追加など、人間が最終チェックを行います。
-
テストを実行し、検証
-
flutter test
などで実行し、成功・失敗を確認。 - 失敗したテストがある場合は、実装やテストコードの修正を行います。
-
-
カバレッジを計測
-
flutter test --coverage
やlcov
を利用してテストカバレッジを測定できます。 - 未カバー部分を洗い出して追加テストを行い、必要に応じてドメインロジックを修正します。
-
CartServiceのコード例
CartService
は以下の責務を持つとします。
- カート内商品を追加・削除
- 合計金額の算出
- 割引クーポンの適用(例:
DISCOUNT10
なら 10%オフ)
詳しい実装は以下
AIへの指示例
Cursorを利用して、「@cart_service.dart このクラスの機能を分析し、このクラスに対するユニットテストをルールに従って書いてください @flutter-unit-testing-best-practices.mdc」というプロンプトで指示しました。
生成AIとの詳しいやり取りログはこちらで確認できます。
AIが出力した最初のテストコードはこちら
テスト修正のポイント
最初のテストは一見通っていますが、一部仕様と矛盾がありました。
- 小数点以下を扱うロジックがあり、日本向けのECアプリにおいて小数点以下を扱わない(円表記)仕様を想定しているので、テスト内容を修正しました。
日本の商習慣では「税込価格の端数を四捨五入」などが一般的ですが、場合によっては海外展開などで小数点以下を扱うこともあります。今回は日本向け仕様でテストしたいので、小数点は発生しない想定として修正し、結果テストが失敗するようになりました。
修正後のテストコードはこちらをご確認ください。端数切り上げで修正してあります。
カバレッジ計測
修正後に flutter test --coverage
を実行し、cart_service.dart
のカバレッジを計測しました。結果は 100% に到達しています。
このように、生成AIが作ったテストを起点にして不足分を補うと、比較的スムーズに高カバレッジを確保できます。
エラー対応とスキップ
今回のテストでは、別仕様の不備が見つかり、Issueを作成して後日対応することにしました。すぐ修正が難しいため、CIが失敗しないように該当テストに skip
引数を渡してスキップしてあります。
たとえば以下のように記述します。
test('特定の仕様検証テスト', () {
// ...テストコード
}, skip: 'Issue #XX で対応予定のため、一時的にスキップ');
これによりCIでのテストは通りますが、仕様修正後にスキップを外すことを忘れないように注意が必要です。
まとめ & 次回予告
• ユニットテストはドメインロジックを守る砦として重要。
• 生成AIを使えば初期のテストコード作成を効率化し、レビューや仕様確認に注力できます。
• 一度テストを整えてしまえば、新機能追加やリファクタリングの際にも大きな不具合を起こしにくいメリットが得られます。
次回の記事では、Widgetテストに焦点を当てます。画面UIや状態管理部分を検証し、ユニットテストだけでは捉えにくいUIの変化や画面遷移などをどのようにテストするか、具体的な例を踏まえて解説していきます。ぜひあわせてご覧ください。
Discussion