testing について
Flutter と Dart のテストを作成・レビューする開発者向けのガイドです。実際の回帰を検出できるテストを重視し、ユニットテスト、ウィジェットテスト、モック、構成を扱います。
主な機能
- 01テストが実際の不具合を検出するか確認
- 02クラス単位でのグループ化とケース命名
- 03ユニットテストとウィジェットテストの選択
- 04ウィジェットテストの作成
- 05mocktail を使ったモックの指針
- 06テストファイルをソース構成に合わせる
活用シーン
- 01ビジネスロジックやリポジトリのユニットテスト
- 02ウィジェットの表示や操作の検証
- 03テストの妥当性とカバレッジのレビュー
- 04不安定または誤検出するテストの調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ evanca/ flutter-ai-rules --skill testingインストール後、Claude Code で/testingを入力して読み込まれたか確認します。
詳細
テストスキル
実際のリグレッションを検出できる、効果的で意味のある Flutter と Dart のテストを作成します。
使用する場面
以下のような場合にこのスキルを使用します。
- ビジネスロジック、リポジトリ、ユーティリティクラスの単体テストを作成するとき。
- UI コンポーネントのウィジェットテストを作成するとき。
- 既存のテストの正確性とカバレッジを確認するとき。
- 不安定なテストや誤検出するテストを修正するとき。
- 単体テスト、ウィジェットテスト、統合テストのどれを使うか判断するとき。
1. テストの妥当性
テストを作成または承認する前に、次のことを確認してください。
「実際のコードが壊れた場合、このテストは本当に失敗するだろうか?」
- モックやフェイクの動作を確認するだけで、実際のロジックを実行しないテストは避けてください。
- 言語や標準ライブラリによって保証されている動作を確認するテストは避けてください。
- すべてのテストは、実際のリグレッションを検出できるものでなければなりません。
// BAD — tests the mock, not real logic
test('should return user', () {
when(() => repo.getUser()).thenReturn(fakeUser);
expect(repo.getUser(), fakeUser); // Only proves the mock works
});
// GOOD — tests the cubit's state transitions driven by the mock
blocTest<UserCubit, UserState>(
'should emit loaded state when getUser succeeds',
build: () {
when(() => repo.getUser()).thenAnswer((_) async => fakeUser);
return UserCubit(repo);
},
act: (cubit) => cubit.fetchUser(),
expect: () => [
const UserState(status: UserStatus.loading),
UserState(status: UserStatus.loaded, user: fakeUser),
],
);
2. 構成
テストファイルでは必ず group() を使用します。グループ名には、テスト対象のクラスを指定します。
group('Counter', () {
late Counter counter;
setUp(() {
counter = Counter();
});
test('value should start at 0', () {
expect(counter.value, 0);
});
test('should increment value by 1', () {
counter.increment();
expect(counter.value, 1);
});
});
ルール:
- 共通のオブジェクト生成には
setUpを、クリーンアップ(ストリームやコントローラーのクローズなど)にはtearDownを使用します。 - 各テストは、1 つの動作に焦点を当てます。
- クラスに多くのメソッドがある場合は、サブ機能ごとに
group()ブロックをネストします。
3. 命名
テストケース名には 「should」 を使い、期待する動作を表します。
test('should emit updated list when item is added', () { ... });
test('should throw ArgumentError when input is negative', () { ... });
4. 単体テストとウィジェットテスト
| 種類 | 対象 | ツール |
|---|---|---|
| 単体テスト | 純粋な Dart ロジック、リポジトリ、cubits/blocs | test、bloc_test、mocktail |
| ウィジェットテスト | 個々のウィジェット、UI の動作、ナビゲーション | flutter_test、WidgetTester |
ビジネスロジックには、原則として 単体テスト を使用します。UI の描画、ジェスチャー処理、ウィジェットの操作を検証する場合は、ウィジェットテスト を使用します。
5. ウィジェットテストのパターン
testWidgets('should display error message on failure', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: BlocProvider<LoginCubit>.value(
value: mockLoginCubit,
child: const LoginView(),
),
),
);
// Simulate failure state
whenListen(
mockLoginCubit,
Stream.fromIterable([const LoginState(status: LoginStatus.failure, errorMessage: 'Invalid')]),
initialState: const LoginState(),
);
await tester.pump();
expect(find.text('Invalid'), findsOneWidget);
});
ルール:
MediaQueryやDirectionalityなどを提供するため、ウィジェットをMaterialApp(またはアプリのルートウィジェット)でラップします。- 1 フレームだけ進める場合は
pump()を、アニメーションを完了させる必要がある場合はpumpAndSettle()を使用します。 - ローカライズされたテキストや動的なテキストを持つ可能性があるウィジェットには、
find.textよりfind.byKeyを優先します。
6. モック作成のベストプラクティス
- モックには
mocktailを使用します(コード生成は不要です)。 any()に渡すカスタム型については、setUpAllでregisterFallbackValue()を呼び出します。- HTTP やデータベースのレイヤーではなく、リポジトリの境界でモックを作成します。
class MockAuthRepository extends Mock implements AuthRepository {}
void main() {
setUpAll(() {
registerFallbackValue(FakeLoginRequest());
});
// ... tests
}
7. テストファイルの整理
test/
feature_a/
cubit/
feature_a_cubit_test.dart
view/
feature_a_view_test.dart
model/
feature_a_model_test.dart
test/の下に、lib/のフォルダー構成を反映します。- ソースファイルごとにテストファイルを 1 つ作成します。
- テストファイルには
<source_file>_test.dartという名前を付けます。
コメント
Markdown 対応