Flutter テスト
このセクションでは、Flutter の3つのテストタイプについて紹介します:ユニットテスト、Widget テスト、統合テストです。
テストタイプ概要
| タイプ | 説明 | 実行コマンド |
|---|---|---|
| ユニットテスト | 単一の関数、クラスのメソッドをテストする | flutter test |
| Widget テスト | 単一の Widget の UI をテストする | flutter test |
| 統合テスト | 完全なアプリケーションフローをテストする | flutter test integration_test/ |
ユニットテスト
ユニットテストはビジネスロジックコードをテストするために使用されます。
例:ユニットテストの例
// math_utils.dart - テスト対象のコード
class MathUtils {
static int add(int a, int b) => a + b;
static int subtract(int a, int b) => a - b;
static int multiply(int a, int b) => a * b;
static double divide(int a, int b) {
if (b == 0) throw ArgumentError('ゼロで割ることはできません');
return a / b;
}
static int factorial(int n) {
if (n < 0) throw ArgumentError('負の数には階乗がありません');
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1);
}
}
// math_utils_test.dart - テストファイル
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/math_utils.dart';
void main() {
group('MathUtils', () {
test('add 2つの数値を加算', () {
expect(MathUtils.add(2, 3), equals(5));
});
test('subtract 2つの数値を減算', () {
expect(MathUtils.subtract(5, 3), equals(2));
});
test('multiply 2つの数値を乗算', () {
expect(MathUtils.multiply(4, 5), equals(20));
});
test('divide 2つの数値を除算', () {
expect(MathUtils.divide(10, 2), equals(5));
});
test('divide ゼロ除算で例外をスロー', () {
expect(() => MathUtils.divide(10, 0), throwsArgumentError);
});
test('factorial 階乗を計算', () {
expect(MathUtils.factorial(5), equals(120));
expect(MathUtils.factorial(0), equals(1));
});
test('factorial 負の数で例外をスロー', () {
expect(() => MathUtils.factorial(-1), throwsArgumentError);
});
});
}
class MathUtils {
static int add(int a, int b) => a + b;
static int subtract(int a, int b) => a - b;
static int multiply(int a, int b) => a * b;
static double divide(int a, int b) {
if (b == 0) throw ArgumentError('ゼロで割ることはできません');
return a / b;
}
static int factorial(int n) {
if (n < 0) throw ArgumentError('負の数には階乗がありません');
if (n == 0 || n == 1) return 1;
return n * factorial(n - 1);
}
}
// math_utils_test.dart - テストファイル
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/math_utils.dart';
void main() {
group('MathUtils', () {
test('add 2つの数値を加算', () {
expect(MathUtils.add(2, 3), equals(5));
});
test('subtract 2つの数値を減算', () {
expect(MathUtils.subtract(5, 3), equals(2));
});
test('multiply 2つの数値を乗算', () {
expect(MathUtils.multiply(4, 5), equals(20));
});
test('divide 2つの数値を除算', () {
expect(MathUtils.divide(10, 2), equals(5));
});
test('divide ゼロ除算で例外をスロー', () {
expect(() => MathUtils.divide(10, 0), throwsArgumentError);
});
test('factorial 階乗を計算', () {
expect(MathUtils.factorial(5), equals(120));
expect(MathUtils.factorial(0), equals(1));
});
test('factorial 負の数で例外をスロー', () {
expect(() => MathUtils.factorial(-1), throwsArgumentError);
});
});
}
Widget テスト
Widget テストは UI コンポーネントをテストするために使用されます。
例:Widget テスト
// counter_widget.dart - テスト対象の Widget
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('カウント: $_count', key: const Key('counter_text')),
ElevatedButton(
key: const Key('increment_button'),
onPressed: _increment,
child: const Text('増加'),
),
],
);
}
}
// counter_widget_test.dart - テストファイル
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter_widget.dart';
void main() {
testWidgets('CounterWidget が初期カウントを表示', (WidgetTester tester) async {
// Widget を構築
await tester.pumpWidget(const MaterialApp(
home: CounterWidget(),
));
// 初期カウント表示を検証
expect(find.text('カウント: 0'), findsOneWidget);
});
testWidgets('CounterWidget ボタンをクリックしてカウントを増加', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: CounterWidget(),
));
// ボタンを見つけてクリック
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pump();
// カウントが更新されたことを検証
expect(find.text('カウント: 1'), findsOneWidget);
// もう一度クリック
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pump();
expect(find.text('カウント: 2'), findsOneWidget);
});
}
class CounterWidget extends StatefulWidget {
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
int _count = 0;
void _increment() {
setState(() {
_count++;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('カウント: $_count', key: const Key('counter_text')),
ElevatedButton(
key: const Key('increment_button'),
onPressed: _increment,
child: const Text('増加'),
),
],
);
}
}
// counter_widget_test.dart - テストファイル
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/counter_widget.dart';
void main() {
testWidgets('CounterWidget が初期カウントを表示', (WidgetTester tester) async {
// Widget を構築
await tester.pumpWidget(const MaterialApp(
home: CounterWidget(),
));
// 初期カウント表示を検証
expect(find.text('カウント: 0'), findsOneWidget);
});
testWidgets('CounterWidget ボタンをクリックしてカウントを増加', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: CounterWidget(),
));
// ボタンを見つけてクリック
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pump();
// カウントが更新されたことを検証
expect(find.text('カウント: 1'), findsOneWidget);
// もう一度クリック
await tester.tap(find.byKey(const Key('increment_button')));
await tester.pump();
expect(find.text('カウント: 2'), findsOneWidget);
});
}
統合テスト
統合テストは完全なアプリケーション機能フローをテストするために使用されます。
例:統合テスト
// integration_test/app_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('アプリ統合テスト', () {
testWidgets('完全なユーザーフローテスト', (WidgetTester tester) async {
// アプリを起動
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
// ホームページの表示を検証
expect(find.text('ホーム'), findsOneWidget);
// クリックして詳細ページへ遷移
await tester.tap(find.text('詳細'));
await tester.pumpAndSettle();
// 詳細ページの表示を検証
expect(find.text('詳細ページ'), findsOneWidget);
// 戻るをクリック
await tester.tap(find.byIcon(Icons.arrow_back));
await tester.pumpAndSettle();
// ホームに戻ったことを検証
expect(find.text('ホーム'), findsOneWidget);
});
});
}
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:my_app/main.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
group('アプリ統合テスト', () {
testWidgets('完全なユーザーフローテスト', (WidgetTester tester) async {
// アプリを起動
await tester.pumpWidget(const MyApp());
await tester.pumpAndSettle();
// ホームページの表示を検証
expect(find.text('ホーム'), findsOneWidget);
// クリックして詳細ページへ遷移
await tester.tap(find.text('詳細'));
await tester.pumpAndSettle();
// 詳細ページの表示を検証
expect(find.text('詳細ページ'), findsOneWidget);
// 戻るをクリック
await tester.tap(find.byIcon(Icons.arrow_back));
await tester.pumpAndSettle();
// ホームに戻ったことを検証
expect(find.text('ホーム'), findsOneWidget);
});
});
}
その他の拡張良好なテストカバレッジはコード品質を向上させることができます。少なくともコアビジネスロジックにはユニットテストを作成することをお勧めします。