mcpField
T
testing

testing

github.com/verygoodopensourceテスト・品質17595 回インストール3 日前に更新
リポジトリ

testing について

Dart・Flutter開発者向けに、VGVの規約を単体テストとウィジェットテストへ適用するスキルです。読みやすい構成、テスト分離、mocktail、ウィジェット用ヘルパー、ゴールデンテスト、実行方法を扱います。

主な機能

  1. 01説明的で階層的なテスト命名
  2. 02分離されたセットアップとプライベートモック
  3. 03mocktailを使うモック規約
  4. 04pumpAppによるウィジェットテスト
  5. 05ゴールデンテストのタグ付けと外観確認
  6. 06test MCPツールの実行指針

活用シーン

  1. 01Dartの単体テストを整理
  2. 02動作を重視したウィジェットテストの作成
  3. 03テスト間の共有状態を削減
  4. 04Flutterモノレポでテストを実行

MCP 対応

test MCPツールを使ったテスト実行の指針があり、directoryとtimeoutの指定方法も扱います。Claude Codeでの選択肢としてmcp__very-good-cli__testを挙げ、サーバーがない場合のコマンドラインによる代替も説明します。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/verygoodopensource/vgv-ai-flutter-plugin --skill testing

インストール後、Claude Code で/testingを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

agents/
openai.yaml
references/
animation-testing.md
configuration.md
coverage.md
golden-tests.md
matchers.md
mocktail.md
test-patterns.md
widget-tests.md

詳細

Dart & Flutter のテスト

Dart と Flutter における VGV のテスト規約です。フレームワークの API は前提とし、以下ではその上に重ねるプロジェクト独自のスタイルを示します。

コア標準

すべてのテスト作業で、次の標準を適用してください。

  • わかりやすいテスト名 — 動作を説明する、詳細で読みやすい名前を付けます。'works' や 'renders' は使わないでください
  • 自然な文として読める階層的な group/test 構造 — クラスには最上位の group、メソッドにはネストした group、動作には test を使います(例:UserRepository → getUser → API が成功したときに User を返す)
  • 型の参照には文字列補間を使用 — 'returns User' ではなく 'returns $User' を使い、名前の変更が自動的に反映されるようにします
  • モックはファイルごとにプライベートにする — ファイル間の結合を防ぐため、アンダースコアを接頭辞に付けて class _MockX extends Mock implements X {} と宣言します
  • テストのセットアップはグループ内にまとめる — すべての setUp/tearDown 呼び出しは group 内に置き、main() のトップレベルには置かないでください
  • 可変オブジェクトは late を使って setUp() で初期化 — late MyDep dep; と宣言してから setUp で代入し、各テストで新しいインスタンスを使えるようにします
  • テスト間で可変状態を共有しない — テスト間で状態が保持される静的メンバー、グローバル変数、トップレベルの final インスタンスは使わないでください
  • package:mocktail を使用 — package:mockito は使わないでください
  • テストタグは定数にする — static const フィールドを持つ abstract class TestTag を使い、タグに生の文字列リテラルを渡さないでください
  • プロパティではなく動作をテスト — ウィジェットテストでは機能上の結果に焦点を当て、静的な視覚プロパティはゴールデンテストで検証します
  • pumpApp テストヘルパーを使用 — test/helpers/pump_app.dart の共通ヘルパーでウィジェットをラップし、pumpWidget(MaterialApp(...)) を直接記述しないでください
  • すべてのゴールデンテストにタグを付ける — ゴールデンテストを個別に実行・更新できるよう、TestTag.golden を付けます
  • プロジェクトがワークスペースのルートにない場合は、test MCP ツールに directory を渡す — Flutter プロジェクトがサブディレクトリ(例:mobile/)にあるモノレポでは、directory: 'mobile' が必要です。pubspec.yaml がワークスペースのルートにある場合に限り、省略してください
  • test MCP ツールに timeout_seconds を渡す — タイムアウトなしで pumpAndSettle() が呼び出されると、Flutter テストが無期限に停止することがあります。停止し続けるのではなく実行が強制終了されるよう、上限(例:timeout_seconds: 120)を設定してください
  • test MCP ツールでクロスハーネスのフォールバックを使用 — Claude Code では mcp__very-good-cli__test を使用します。このプラグインの Bash フックも MCP サーバーへの接続もない環境では、同じカバレッジおよびタイムアウトのオプションを指定して very_good test(または flutter test/dart test)を直接実行してください。MCP サーバーが見つからない場合でも処理を止めないでください

ファイル構成

規約ルール
ファイルの接尾辞すべてのテストファイルは _test.dart で終わります
ディレクトリすべてのテストは test/ の下に置きます
構成のミラーtest/ は lib/ の構成を正確に反映します — lib/src/models/user.dart → test/src/models/user_test.dart
ヘルパー共通のテストユーティリティは test/helpers/ に置きます(例:pump_app.dart、fakes.dart)

Group と Test の階層

連結した説明が自然な文として読めるように、グループを構成してください。最上位のグループでは、文字列ではなく PascalCase の型そのものを使います。

class _MockApiClient extends Mock implements ApiClient {}

void main() {
  group(UserRepository, () {
    late ApiClient apiClient;
    late UserRepository subject;

    setUp(() {
      apiClient = _MockApiClient();
      subject = UserRepository(apiClient: apiClient);
    });

    group('getUser', () {
      test('returns $User when API call succeeds', () async {
        when(() => apiClient.fetchUser(any()))
            .thenAnswer((_) async => User(id: '1', name: 'Dash'));

        final result = await subject.getUser('1');

        expect(result, equals(User(id: '1', name: 'Dash')));
        verify(() => apiClient.fetchUser('1')).called(1);
      });

      test('throws $UserNotFoundException when API returns 404', () {
        when(() => apiClient.fetchUser(any()))
            .thenThrow(ApiException(statusCode: 404));

        expect(
          () => subject.getUser('1'),
          throwsA(isA<UserNotFoundException>()),
        );
      });
    });
  });
}
命名規則
パターン例
値を返す'returns $User when API call succeeds'
例外をスローする'throws $UserNotFoundException when user is not found'
依存関係を呼び出す'calls apiClient.deleteUser with correct id'
状態を出力する'emits [loading, success] when data is fetched'
条件付きの動作'returns cached value when cache is not expired'
エッジケース'returns empty list when repository has no items'

テストの分離

各テストは、単独で、任意の順序で、並列で実行した場合にも合格する必要があります。隠れた依存関係を見つけるには、 --test-randomize-ordering-seed random を使用します。

アンチパターン問題正しい方法
main() のトップレベルにある setUp最適化のためにテストランナーがファイルを統合すると動作しなくなりますsetUp を group の中に移動する
final dep = _MockDep();(トップレベル)すべてのテストで同じインスタンスが共有され、状態が漏れますgroup 内で late と setUp を使用する
class MockDep extends Mock(パブリック)他のテストファイルからインポートされ、依存される可能性がありますclass _MockDep extends Mock(プライベート)を使用する
静的/グローバルな可変変数テスト間で状態が保持されますsetUp でリセットするか、完全に避ける
特定の順序で実行する必要があるテスト不安定で、ランダムな順序では失敗します各テストを完全に自己完結させる

プライベートロジックのテスト

プライベートメソッドを直接テストしてはいけません。プライベートロジックを使用するパブリックメソッドを通じて実行します。

// If _normalizeEmail is private, test it through the public createUser method:
test('normalizes email to lowercase before saving', () async {
  when(() => repository.save(any())).thenAnswer((_) async {});

  await subject.createUser(email: '[email protected]');

  final captured = verify(() => repository.save(captureAny())).captured;
  expect(captured.first.email, equals('[email protected]'));
});

ウィジェットのテスト

ルール詳細
testWidgets を使用するすべてのウィジェットテストで、test ではなく testWidgets を使用します
find.byType を優先するデフォルトのファインダーです。ユーザーに表示されるコンテンツには find.text を使用し、型やテキストで判別できない場合にのみ find.byKey を使用します
動作のカテゴリごとにグループ化するネストしたグループ名には renders、navigates、calls [MethodName]、updates を使用します
動作に注目するウィジェットの 動作(テキストの表示、コールバックの呼び出し、画面遷移)を検証します。見た目にはゴールデンテストを使用します
Bloc と Cubit をモックするpackage:bloc_test の MockBloc/MockCubit を使用します。ウィジェットテストで実際の Bloc を使用してはいけません
pumpAndSettle() より pump() を優先するpumpAndSettle は CircularProgressIndicator などの無限アニメーションで停止することがあります
pumpApp ヘルパー

すべてのウィジェットテストで、共通のヘルパーを通じてテスト対象のウィジェットをラップし、各テストで独自の MaterialApp を直接記述しないようにします。

// test/helpers/pump_app.dart
extension PumpApp on WidgetTester {
  Future<void> pumpApp(Widget widget) {
    return pumpWidget(
      MaterialApp(
        home: widget,
      ),
    );
  }
}

test/helpers/helpers.dart からエクスポートすると、各テストで1行のインポートを使えます。

import '../helpers/helpers.dart';

void main() {
  group(MyWidget, () {
    testWidgets('renders greeting text', (tester) async {
      await tester.pumpApp(const MyWidget());

      expect(find.text('Hello'), findsOneWidget);
    });
  });
}
ウィジェットテストのアンチパターン
アンチパターン問題正しい方法
各テストにインラインで MaterialApp を記述ボイラープレートが重複し、セットアップに一貫性がなくなりますpumpApp ヘルパーを使用します
デフォルトのファインダーとして find.byKey を使用テストが実装上のキーに依存しますfind.byType または find.text を優先します
パディング、色、フォントサイズをテスト脆弱で、デザインを少し調整するたびに失敗し、動作ではなく見た目を検証します見た目の検証にはゴールデンテストを使用します
インタラクション後に pump() を呼び出さないウィジェットツリーが再構築されず、アサーションで古い状態が確認されますtap、enterText などの後は必ず pump() を呼び出します
ウィジェットテストで実際の Bloc を使用テストが統合テストになり、遅く、脆く、分離が難しくなりますbloc_test の MockBloc/MockCubit を使用します

パディング、背景色、フォントサイズは、繰り返しテストしたくなる項目です。ウィジェットテストでこれらをアサートすると、テストがデザインシステムに依存し、スタイルを変更するたびに失敗します。これらは TestTag.golden タグを付けたゴールデンテストに移してください。

その他のリソース

  • references/mocktail.md — モックとフェイク、スタブ設定、引数マッチャー、検証、フォールバック値
  • references/test-patterns.md — setUp/tearDown のライフサイクルと、非同期処理、ストリーム、例外、Equatable、コールバックのパターン
  • references/widget-tests.md — Bloc を使用した完全なウィジェットテスト、pumpApp でのテーマ/ローカライズ、ポンピングメソッド、ファインダー、インタラクション
  • references/golden-tests.md — ゴールデンファイルテスト(セットアップ、ゴールデンの作成、タグ付け、実行/更新、アンチパターン)
  • references/matchers.md — マッチャーのクイックリファレンス
  • references/configuration.md — dart_test.yaml の設定(タグ、プラットフォームのオーバーライド)と MCP test ツールを使ったテストの実行
  • references/coverage.md — カバレッジのパターンとパッケージ/インポートのリファレンス
  • references/animation-testing.md — 暗黙的/明示的なアニメーション、AnimatedSwitcher、ページ遷移、注入されたコントローラーのテスト

コメント