はじめに
前回はTodoアプリをローカルデータに保存するところまで実施しました。

今回はテストについて学びます。
アプリを開発する際、テストは非常に重要です。テストはアプリの品質を保つため、Flutterには、テストをサポートする便利なツールが用意されています。
今回の記事では、Flutterの基本的な単体テスト(ユニットテスト)とウィジェットテストの実装方法を学びます。初心者でも理解できるよう、ステップごとに解説していきますので、ぜひ一緒に進めていきましょう。
テストの基本
テストは、アプリのバグを防ぎ、品質を保つために不可欠です。Flutterでは、簡単にテストを導入でき、ユニットテストやウィジェットテスト、統合テストがサポートされています。
単体テスト(ユニットテスト)
ユニットテストは、アプリの特定の関数やメソッドが正しく動作するかを確認するためのテストです。ロジック部分をテストすることで、予期せぬ動作を防ぎます。
テスト環境のセットアップ
プロジェクトを作成した時点で、セットアップは完了していますので、追加のセットアップは必要ありません。もし完了していない場合は、参考にしてください。
まず、test
ディレクトリを作成し、テストコードを配置します。
mkdir test
次に、Flutterプロジェクトのpubspec.yaml
ファイルで、test
パッケージが含まれていることを確認します。通常、デフォルトで追加されています。
dev_dependencies:
flutter_test:
sdk: flutter
単体テストの実装例
下記は簡単な足し算を行う関数にです。
// lib/calculator.dart
class Calculator {
int add(int a, int b) {
return a + b;
}
}
次に、この関数をテストします。
test()
: 個別のテストケースを定義します。expect()
: 結果が期待通りかどうかを確認します。例えば、expect(actual, matcher)
の形式で、actual
がmatcher
と一致しているかチェックします。
// test/calculator_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:your_project/calculator.dart';
void main() {
test('足し算のテスト', () {
final calculator = Calculator();
expect(calculator.add(2, 3), 5);
expect(calculator.add(-1, 1), 0);
});
}
flutter test
コマンドを実行して、テストが正常に動作することを確認します。
flutter test
00:01 +0: 足し算のテスト 00:01 +1: 足し算のテスト 00:01 +1: All tests passed!
ウィジェットテスト
ウィジェットテストは、Flutterのウィジェット(UI)をテストするための方法です。個々のウィジェットが正しく動作し、期待通りにレンダリングされるかを確認します。
ウィジェットテストの実装例
次に、Hello World
を表示する簡単なウィジェットに対するテストを行ってみます。
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: Text('Hello World App')),
body: Center(child: Text('Hello World')),
),
);
}
}
このウィジェットをテストするコードをtest/main_test.dart
に記述します。
testWidgets()
: ウィジェットテストを行うための関数。WidgetTester
: テスト中にウィジェットを操作するためのユーティリティ。tap
やpump
を使って、ウィジェットを操作・再描画します。find.text()
やfind.byType()
: ウィジェットを見つけるためのヘルパー関数。
// test/main_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:your_project/main.dart';
void main() {
testWidgets('Hello Worldが表示されるかのテスト', (WidgetTester tester) async {
// アプリをビルド
await tester.pumpWidget(MyApp());
// "Hello World"というテキストが表示されているかを確認
expect(find.text('Hello World'), findsOneWidget);
});
}
このテストは、ウィジェットが期待通りに描画され、Hello World
というテキストが正しく表示されているかを確認します。テストを実行するには、再びflutter test
コマンドを使います。
flutter test
00:01 +0: Hello Worldが表示されるかのテスト 00:01 +1: Hello Worldが表示されるかのテスト 00:01 +1: All tests passed!
Todoアプリのテスト
前回まで作成したTodoアプリのテストを作ってみます。
今回作成したTodoアプリ内にある_TodoListScreenState
は、アンダースコア(_
)がついているためプライベートクラスとして定義されています。このため、クラスは同じファイル内からしかアクセスできません。そのため、テストファイルから直接アクセスできません。
そのためウィジェットテストでの間接的なテストを行います。
これにより、画面の操作(追加、削除、編集など)をシミュレートし、テストできます。
テスト
testWidgets
: ウィジェットテストを行うための関数。これにより、TodoApp
全体をビルドし、その中でユーザー操作(タップや入力)をシミュレートしてテストします。tester.pumpWidget
:TodoApp
をウィジェットツリーに挿入して、テスト対象とします。tester.tap
: ボタンをタップする操作をシミュレートします。tester.enterText
: テキストフィールドにテキストを入力します。tester.pumpAndSettle
: すべてのウィジェットのアニメーションや再描画が完了するまで待ちます。
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:my_first_app/main.dart';
void main() {
setUp(() async {
SharedPreferences.setMockInitialValues({}); // SharedPreferencesをモックで初期化
});
testWidgets('Todo item is added correctly', (WidgetTester tester) async {
await tester.pumpWidget(TodoApp());
// FloatingActionButtonを押して、新しいタスクを追加
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle(); // ダイアログの描画を待つ
// テキストフィールドにタスクを入力
await tester.enterText(find.byType(TextField), 'New Task');
await tester.tap(find.text('Add'));
await tester.pumpAndSettle(); // タスク追加後の描画を待つ
// タスクがリストに追加されたか確認
expect(find.text('New Task'), findsOneWidget);
});
testWidgets('Todo item is removed correctly', (WidgetTester tester) async {
await tester.pumpWidget(TodoApp());
// 新しいタスクを追加
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Task to remove');
await tester.tap(find.text('Add'));
await tester.pumpAndSettle();
// リストにタスクが表示されていることを確認
expect(find.text('Task to remove'), findsOneWidget);
// 削除ボタンを押してタスクを削除
await tester.tap(find.byIcon(Icons.delete));
await tester.pumpAndSettle();
// タスクが削除されたことを確認
expect(find.text('Task to remove'), findsNothing);
});
testWidgets('Todo item is edited correctly', (WidgetTester tester) async {
await tester.pumpWidget(TodoApp());
// 新しいタスクを追加
await tester.tap(find.byType(FloatingActionButton));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Task to edit');
await tester.tap(find.text('Add'));
await tester.pumpAndSettle();
// リストにタスクが表示されていることを確認
expect(find.text('Task to edit'), findsOneWidget);
// リストアイテムをタップして編集ダイアログを開く
await tester.tap(find.text('Task to edit'));
await tester.pumpAndSettle();
await tester.enterText(find.byType(TextField), 'Edited Task');
await tester.tap(find.text('Save'));
await tester.pumpAndSettle();
// タスクが編集されたことを確認
expect(find.text('Edited Task'), findsOneWidget);
expect(find.text('Task to edit'), findsNothing);
});
}
flutter run
00:02 +3: All tests passed!
テストとデバッグのコツ
テスト駆動開発(TDD)の導入
テスト駆動開発(TDD)とは、テストを先に書き、そのテストをパスするようにコードを実装していく開発手法です。これにより、バグの少ない、堅牢なコードを効率的に開発できます。
自動テストの活用
手動でのテストは時間がかかるため、自動テストを活用しましょう。Flutterでは、簡単にテストを自動化でき、CI/CDパイプラインに組み込むことも可能です。
4. まとめ
今回の記事では、Flutterの単体テスト、ウィジェットテストの基本的な使い方を解説しました。これらのツールを活用することで、アプリの品質を保ち、効率的に問題を発見し解決することができます。
次回は、いよいよリリースしていきます。
次回はこちら

コメント