Appearance
Metanaka .NET Testing
Rule Levels
[Required]: 違反は必須修正として扱う。[Recommended]: 原則として採用する。合理的な理由がある逸脱は許容する。[Contextual]: 記載した条件が成立するときだけ適用する。根拠が不足する場合は違反ではなく確認事項として扱う。
レビュー判定に使う規則はlevel tag付きの箇条書きだけとする。tagのない文章とcode sampleは背景、理由、例を示す。
Responsibility Boundary
This skill defines the structure and verification content of completed test code. It does not define test-list creation, RED/GREEN/REFACTOR order, console markers, or cycle reporting; those belong to metanaka-dotnet-tdd.
ShouldlyとNSubstituteを組み合わせた代表例が必要な場合だけsamples/handler/README.mdと、そこから案内するsourceを読む。Value Objectの境界値を分ける例が必要な場合だけsamples/value-object/README.mdと、そこから案内するsourceを読む。sampleに登場するassertionやmockを無関係なbehaviorへ機械的に増やさない。
Tests
[Required]Backend testは対応するtest projectに置き、production project rootからの相対pathをtest project root配下で再現する。
text
<test-project>/
Features/
Rooms/
Join/
RoomJoinEndpointTests.cs
RoomJoinHandlerTests.cs[Required]test classはprimary production typeを1つ持ち、その型名に対応する{Type}Testsにする。別のtop-level production typeを直接対象とする独立した振る舞いは、その型のtest classへ置く。[Contextual]primary typeとcollaboratorの協調動作を1つのpublic behaviorとして検証するtestは、型ごとに機械的に分割しない。private nested comparer、fixture、builder、小さなhelperは、それを所有するtest classと同じfileに置いてよい。[Required]test frameworkはxUnit v3、assertion libraryはShouldlyを使う。[Required]test methodの識別子は英語のPascalCaseにする。日本語や日本語混じりのC# method nameは使わない。[Required][Fact(DisplayName = "...")]または[Theory(DisplayName = "...")]を使い、観測可能な事前条件と結果を仕様として表す。既存fileを変更するときは、そのfileのDisplayNameの言語に合わせる。新規fileで指定がなければ日本語を使う。[Required]日本語のDisplayNameは「〜すること」「〜を返すこと」「〜しないこと」のように、期待する振る舞いを述語まで含む文にする。特定のsuffixは必須にしない。[Required]assertionのないtestや、意図が読めない広すぎるintegration testを書かない。
Assertions
[Required]意図を直接表すShouldly assertionがある場合はそれを使い、booleanへ変換したShouldBeTrue()で代用しない。collectionはShouldBeEmpty、ShouldHaveSingleItem、ShouldContain、型はShouldBeOfType<T>、例外はShould.Throw/Should.ThrowAsyncを使う。[Required]ErrorOr<T>の失敗はIsErrorだけで終えず、対象behaviorを識別するError.TypeとError.Codeを検証する。[Contextual]ErrorOr<T>の成功で返された値または状態変更が対象behaviorの結果なら、それも検証する。[Contextual]collectionの順序がcontractなら順序を含めて比較し、順序がcontractでない場合は順序非依存のassertionを使う。実装が偶然返した順序を仕様にしない。[Contextual]例外behaviorでは例外型に加え、利用者または障害解析に必要なmessage、inner exception、状態不変のうちcontractであるものを検証する。
Test Design
[Required]Feature-local model testは、対象behaviorに存在する入力、戻り値、状態遷移、不変条件、domain errorを直接検証する。対象behaviorに存在しない観測結果を作って検証しない。[Required]Value ObjectのCreate/Ensureは成功値、代表的な失敗、境界値を分けて検証する。[Contextual]Handlerが依存するrepository、TimeProvider、logger、external service portは、その協調動作を検証するときに差し替える。[Contextual]Handler testでは対象behaviorに応じてErrorOr<T>の業務エラー、成功時response、下流へ渡る同じCancellationToken、TimeProvider由来のUTC時刻を検証する。[Recommended]Handlerの新規test suite全体を設計する場合は、成功resultと、対象分岐から直接返り得る業務エラーを最低ラインとして検証する。既存suiteの一つのbehaviorを変更する場合は、既存testを含むsuite全体で満たせばよく、今回のcycleへ無関係なtestを追加しない。新規suiteでは、認可、not found、競合、状態不正など結果のtypeまたはcodeが異なる各分岐を1つずつ代表させる。[Required]Validator testはHTTP request DTOに対するvalidation resultを検証し、feature-local model / Value Objectと同じ規則を二重実装しない。[Contextual]Endpoint testは変更したbehaviorに応じてvalidation、typed result、status code、response body、error mapping、authorization、OpenAPI metadataを検証する。[Contextual]Infrastructure testは変更した永続化・adapter behaviorを検証し、Featuresへprovider SDK型が漏れていないことを確認する。document path、provider transactionの再実行、server timestamp、serialization属性、emulatorでしか再現できないquery・index・atomicityがcontractならintegration testを使い、純粋なmappingや引数変換だけならunit testに留める。
Test Doubles
[Contextual]repository port、external service port、loggerなどの外部境界をmockにする場合はNSubstituteを使う。[Required]mock verificationはobservable behaviorに必要なinteractionだけに限定する。保存された値、同じtokenの伝播、外部副作用の抑止などを検証し、内部の呼び出し順や無関係なgetterまで固定しない。[Contextual]commandを一度だけ送ること自体がcontractならReceived(1)、失敗時に副作用を起こさないことがcontractならDidNotReceiveを使う。単なるstub取得は回数検証せず、戻り値または状態のassertionを優先する。[Required]NSubstituteのArg.Any<T>()で重要な引数を隠さない。対象behaviorが値、UTC時刻、ID、またはCancellationTokenを規定する場合は具体値かpredicateで検証する。[Contextual]現在時刻へ依存するbehaviorでは、TimeProviderを固定時刻を返すfake / test doubleにする。[Contextual]cancellation propagationを持つbehaviorでは、CancellationTokenSourceから作ったtokenを渡し、必要な下流callに同じtokenが渡ることを検証する。[Required]Feature-local modelの純粋ロジックではmockを使わない。[Contextual]Firestoreのdocument path、transaction再実行、server timestamp、serialization、query、index、atomicityがcontractなら、in-memory doubleだけで済ませずemulatorを使う。primitiveとのmappingやpath文字列の組み立てだけが対象ならunit testに留める。[Contextual]Firestore transaction再実行を検証する場合はtest固有のdocumentを使い、最初のcallbackをgateで停止して競合writeを完了してから解放するなど、競合を決定的に発生させる。時間依存の競争にせずtimeoutを設け、最終的なatomic stateを検証する。callback回数は再実行自体がcontractの場合だけ検証する。
Isolation and Determinism
[Required]testは実行順序と他testの実行結果に依存させない。変更可能なstatic state、共有collection、環境変数、clockをtest間で暗黙共有しない。[Contextual]高価なemulator、server、database fixtureだけをIClassFixture/ collection fixtureで共有してよい。各testの業務dataはtestごとに作成し、共有fixtureを使う場合は初期化・cleanupと並列実行可否をfixture側で明示する。[Required]固定できる時刻、GUID、乱数seed、入力順序は固定する。randomized testを使う場合は失敗を再現できるseedを保持・報告し、randomnessだけを境界値testの代替にしない。[Required]並列実行を無効にするのは、隔離できない外部resourceやprocess-wide stateを共有するtest collectionに限定する。suite全体を無効にする場合は、そのresourceと理由を明記する。