dart-matcher-best-practices について
package:matcher を使った Dart テストの作成と改善を支援するスキルです。コレクション、型、プロパティ、非同期処理、例外のアサーションを扱い、既存のチェックを変更しない条件も示します。
主な機能
- 01コレクション用マッチャーの選択
- 02型とオブジェクトプロパティの検証
- 03非同期結果のアサーション
- 04throwsA による例外テスト
- 05リファクタリングを避ける条件の判別
活用シーン
- 01手動の長さチェックを hasLength に置換
- 02Map 検索アサーションの改善
- 03Future の結果と例外のテスト
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ kevmoo/ dash_skills --skill dart-matcher-best-practicesインストール後、Claude Code で/dart-matcher-best-practicesを入力して読み込まれたか確認します。
同梱ファイルevals/evals.json
詳細
Dart Matcher のベストプラクティス
このスキルを使用する場面
次の場合にこのスキルを使用してください。
expectとpackage:matcherを使ってアサーションを記述する場合。- レガシーな手動チェックを、よりすっきりしたマッチャーに移行する場合。
- わかりにくいテスト失敗をデバッグする場合。
使用しない場面(適用を控えるためのガードレール)
次の場合は、このスキルを適用したりアサーションをリファクタリングしたりしないでください。
- コレクション以外のプロパティ:プロパティ名が
.lengthでも、コレクション以外のクラスに属している場合(例:Vectorの幾何学的なスカラーの大きさ、バッファーの長さ、線分の距離)。hasLength(n)が想定するのは、Iterable、Map、またはStringのintです。 - 独自ドメインの真偽値:任意のドメインプロパティを検査する真偽値チェックの場合(例:
expect(switch.isEnabled, isTrue))。存在しないマッチャーを作らないでください。 - 別のアサーションフレームワーク:パッケージで
package:checks(check(x)...)を採用している場合。checksをpackage:matcherに戻さないでください。 - すでに慣用的なアサーション:テストですでに宣言的なマッチャー(
hasLength、isEmpty、isNotEmpty、contains、containsPair、isA、throwsA)を使用している場合。適用を控え、変更を一切加えないでください。
検出
マッチャーの使い方を改善できる箇所を見つけるには、最適でないパターンを検索します。
最適でない長さチェック
hasLength を使用すべき長さチェックを検索します。
- 正規表現:
expect\([^,]+.length,\s*
最適でない真偽値チェック
専用のマッチャーがある真偽値プロパティのチェックを検索します。
- 正規表現:
expect\([^,]+.isEmpty,\s*(true|equals\(true\)) - 正規表現:
expect\([^,]+.isNotEmpty,\s*(true|equals\(true\)) - 正規表現:
expect\([^,]+.contains\(.*\),\s*(true|equals\(true\))
最適でない Map の検索
containsPair を使わずに手動で Map を検索している箇所を検索します。
- 正規表現:
expect\([^,]+\[.*\],\s*
基本的なマッチャー
1. コレクション(hasLength、contains、isEmpty、unorderedEquals、containsPair)
-
hasLength(n):expect(list.length, n)よりもexpect(list, hasLength(n))を優先します。- 失敗時のエラーメッセージが改善されます(実際のリストの内容が表示されます)。
-
isEmpty/isNotEmpty:expect(list.isEmpty, true)よりもexpect(list, isEmpty)を優先します。expect(list.isNotEmpty, true)よりもexpect(list, isNotEmpty)を優先します。
-
contains(item):- 手動で反復処理せずに、存在を確認できます。
expect(list.contains(item), true)より優先します。
-
unorderedEquals(items):- 順序に関係なく内容を確認できます。
expect(list, containsAll(items))より優先します。
-
containsPair(key, value):- Map に特定のキーと値のペアが含まれていることを確認します。
expect(map[key], value)やexpect(map.containsKey(key), true)による確認より優先します。
2. 型チェック(isA<T> と TypeMatcher<T>)
-
isA<T>():- インラインアサーションに適しています:
expect(obj, isA<Type>())。 TypeMatcher<Type>()より簡潔で読みやすくなります。.having()を使って制約を連結できます。
- インラインアサーションに適しています:
-
TypeMatcher<T>:- トップレベルで再利用可能なマッチャーを定義する場合に適しています。
constを使用してください:const isMyType = TypeMatcher<MyType>();- ここでも
.having()を連結できますが、生成されるマッチャーはconstではありません。
3. オブジェクトのプロパティ(having)
プロパティを確認するには、isA<T>() またはほかの TypeMatcher で .having() を使用します。
- 説明的な名前:読みやすさを高めるため、クロージャー内では
p0のような汎用的な名前ではなく、(e) => e.messageのような意味のあるパラメーター名を使用します。
expect(person, isA<Person>()
.having((p) => p.name, 'name', 'Alice')
.having((p) => p.age, 'age', greaterThan(18)));
これにより、どのプロパティが失敗したかを正確に示す、詳細な失敗メッセージが表示されます。
4. 非同期アサーション
-
completion(matcher):- Future の完了を待ち、その値を確認します。
- テストを続行する前に Future が完了するよう、
await expectLater(...)を優先してください。 await expectLater(future, completion(equals(42)))。
-
throwsA(matcher):- Future または関数が例外をスローすることを確認します。
await expectLater(future, throwsA(isA<StateError>()))。expect(() => function(), throwsA(isA<ArgumentError>()))(同期関数が例外をスローする場合はexpectで問題ありません)。
5. 例外のテスト(try/catch と fail() の併用を避ける)
- 命令的な
try/catchよりthrowsAを優先してください:- 古いテストでは、次のように書かれていることがよくあります。
try { fn(); fail('should throw'); } catch (e) { expect(e, isA<T>()); }。 - 宣言的なマッチャーに置き換えてください。
// 同期処理: expect(() => parseData('invalid'), throwsA(isA<FormatException>())); // 非同期処理: await expectLater(client.fetch('bad-url'), throwsA(isA<HttpException>())); - よく使用される組み込みの例外マッチャーには、
throwsArgumentError、throwsStateError、throwsRangeError、throwsFormatException、throwsNoSuchMethodError、throwsUnsupportedErrorがあります。
- 古いテストでは、次のように書かれていることがよくあります。
6. expectLater の使用
非同期の動作をテストする際は、処理が適切な順序で実行されるよう、await expectLater(...) を使用してください。
// GOOD: Waits for future to complete before checking side effects
await expectLater(future, completion(equals(42)));
expect(sideEffectState, equals('done'));
// BAD: Side effect check might run before future completes
expect(future, completion(equals(42)));
expect(sideEffectState, equals('done')); // Race condition!
原則
- 失敗時のわかりやすさ:明確なエラーメッセージを生成するマッチャーを選んでください。
- 手動ロジックを避ける:アサーションに
if文やforループを使用せず、マッチャーに処理させてください。 - 具体的なマッチャー:利用可能な中で最も具体的なマッチャーを使用してください(例:マップのキーを手動で確認する代わりに
containsPairを使用します)。
関連スキル
- dart-test-fundamentals:テストの構成、ライフサイクル、設定に関する基本概念です。
コメント
Markdown 対応