Skip to content

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はShouldBeEmptyShouldHaveSingleItemShouldContain、型はShouldBeOfType<T>、例外はShould.Throw / Should.ThrowAsyncを使う。
  • [Required] ErrorOr<T>の失敗はIsErrorだけで終えず、対象behaviorを識別するError.TypeError.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、下流へ渡る同じCancellationTokenTimeProvider由来の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と理由を明記する。