dotnet-test-frameworks について
.NET 開発者やコードレビュー用ツールがテストを識別し、フレームワーク固有の規約を参照するための資料です。MSTest、xUnit、NUnit、TUnit のアサーション、ライフサイクルフック、統合テストのマーカーを扱います。
主な機能
- 01テストクラスとメソッドの識別
- 024種類のフレームワークのアサーション比較
- 03スキップと無視の属性を確認
- 04統合テストと外部依存の識別
- 05セットアップと後処理の規約を参照
活用シーン
- 01.NET コードベースのテスト分類
- 02フレームワーク標準の例外アサーションを提案
- 03テストの外部依存をレビュー
- 04テストのライフサイクルとスキップ動作を確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill dotnet-test-frameworksインストール後、Claude Code で/dotnet-test-frameworksを入力して読み込まれたか確認します。
詳細
.NET テストフレームワークリファレンス
.NET テストフレームワーク(MSTest、xUnit、NUnit、TUnit)に固有の検出パターンです。
テストファイルの識別
| フレームワーク | テストクラスのマーカー | テストメソッドのマーカー |
|---|---|---|
| MSTest | [TestClass] | [TestMethod]、[DataTestMethod] |
| xUnit | (なし — 規約ベース) | [Fact]、[Theory] |
| NUnit | [TestFixture] | [Test]、[TestCase]、[TestCaseSource] |
| TUnit | (なし — 規約ベース) | [Test] |
フレームワーク別のアサーション API
| カテゴリ | MSTest | xUnit | NUnit | TUnit |
|---|---|---|---|---|
| 等値 | Assert.AreEqual | Assert.Equal | Assert.That(x, Is.EqualTo(y)) | await Assert.That(x).IsEqualTo(y) |
| 真偽値 | Assert.IsTrue / Assert.IsFalse | Assert.True / Assert.False | Assert.That(x, Is.True) | await Assert.That(x).IsTrue() / await Assert.That(x).IsFalse() |
| null | Assert.IsNull / Assert.IsNotNull | Assert.Null / Assert.NotNull | Assert.That(x, Is.Null) | await Assert.That(x).IsNull() / await Assert.That(x).IsNotNull() |
| 例外 | Assert.Throws<T>() / Assert.ThrowsExactly<T>() | Assert.Throws<T>() | Assert.That(() => ..., Throws.TypeOf<T>()) | await Assert.That(() => ...).Throws<T>() / await Assert.That(() => ...).ThrowsExactly<T>() |
| コレクション | CollectionAssert.Contains | Assert.Contains | Assert.That(col, Has.Member(x)) | await Assert.That(col).Contains(x) |
| 文字列 | StringAssert.Contains | Assert.Contains(str, sub) | Assert.That(str, Does.Contain(sub)) | await Assert.That(str).Contains(sub) |
| 型 | Assert.IsInstanceOfType | Assert.IsAssignableFrom | Assert.That(x, Is.InstanceOf<T>()) | await Assert.That(x).IsAssignableTo<T>()(型の完全一致チェックには await Assert.That(x).IsTypeOf<T>() を使用) |
| 判定不能 | Assert.Inconclusive() | [Fact(Skip)] でスキップ | Assert.Inconclusive() | Skip.Test("reason")(実際の判定不能状態はありません) |
| 失敗 | Assert.Fail() | Assert.Fail()(.NET 10+) | Assert.Fail() | Assert.Fail() |
TUnit 固有: アサーションは非同期で、必ず await する必要があります。await を忘れるとアサーションが実行されず、テストは何も通知せずに成功します。組み込みアナライザーが await の不足を警告します。複数のアサーションは .And / .Or のチェーンで組み合わせるか、Assert.Multiple() でグループ化できます。
サードパーティ製のアサーションライブラリ:Should*(Shouldly)、.Should()(FluentAssertions / AwesomeAssertions)、Verify()(Verify)。TUnit には、同じインフラストラクチャ上で FluentAssertions 形式の value.Should().BeEqualTo(...) を提供する、オプションの TUnit.Assertions.Should パッケージも付属しています。
スリープ/遅延パターン
| パターン | 例 |
|---|---|
| スレッドのスリープ | Thread.Sleep(2000) |
| タスクの遅延 | await Task.Delay(1000) |
| SpinWait | SpinWait.SpinUntil(() => condition, timeout) |
スキップ/無視の属性
| フレームワーク | 属性 | 理由付き |
|---|---|---|
| MSTest | [Ignore] | [Ignore("reason")] |
| xUnit | [Fact(Skip = "reason")] | (理由は必須です) |
| NUnit | [Ignore("reason")] | (理由は必須です) |
| TUnit | [Skip("reason")] | (理由は必須です。クラスおよびアセンブリのスコープでも有効です。例:[assembly: Skip("…")]。テスト中の動的なスキップには Skip.Test("reason") を使用します。) |
| 条件付き | #if false / #if NEVER | (理由は指定できません) |
例外処理 — 慣用的な代替方法
テストで例外を検証するために try / catch を使っている場合は、フレームワーク標準の代替方法を提案します。
MSTest:
// Instead of try/catch (matches exact type):
var ex = Assert.ThrowsExactly<InvalidOperationException>(
() => processor.ProcessOrder(emptyOrder));
Assert.AreEqual("Order must contain at least one item", ex.Message);
// Or (also matches derived types):
var ex = Assert.Throws<InvalidOperationException>(
() => processor.ProcessOrder(emptyOrder));
Assert.AreEqual("Order must contain at least one item", ex.Message);
xUnit:
var ex = Assert.Throws<InvalidOperationException>(
() => processor.ProcessOrder(emptyOrder));
Assert.Equal("Order must contain at least one item", ex.Message);
NUnit:
var ex = Assert.Throws<InvalidOperationException>(
() => processor.ProcessOrder(emptyOrder));
Assert.That(ex.Message, Is.EqualTo("Order must contain at least one item"));
TUnit:
await Assert.That(() => processor.ProcessOrder(emptyOrder))
.Throws<InvalidOperationException>()
.WithMessage("Order must contain at least one item");
// Or, for exact-type matching (no derived types):
await Assert.That(() => processor.ProcessOrder(emptyOrder))
.ThrowsExactly<InvalidOperationException>();
不審な兆候 — .NET でよく見られるパターン
| 不審な兆候 | 確認する内容 |
|---|---|
| ファイルシステム | File.ReadAllText、File.Exists、File.WriteAllBytes、Directory.GetFiles、ハードコードされたパスを使用する Path.Combine |
| データベース | SqlConnection、DbContext(インメモリプロバイダーを使用していないもの)、SqlCommand |
| ネットワーク | HttpMessageHandler をオーバーライドせずに使用する HttpClient、WebRequest、TcpClient |
| 環境 | Environment.GetEnvironmentVariable、Environment.CurrentDirectory |
| 許容されるもの | MemoryStream、StringReader、インメモリデータベースプロバイダー、カスタムの DelegatingHandler |
統合テストの識別子
以下に該当するものは統合テストとして認識してください(不審度を適宜調整してください)。
- クラス名に
Integration、E2E、EndToEnd、またはAcceptanceが含まれる [TestCategory("Integration")](MSTest)[Trait("Category", "Integration")](xUnit)[Category("Integration")](NUnit、TUnit)- プロジェクト名が
.IntegrationTestsまたは.E2ETestsで終わる
セットアップ/クリーンアップメソッド
| フレームワーク | セットアップ | クリーンアップ |
|---|---|---|
| MSTest | [TestInitialize] またはコンストラクター | [TestCleanup] または IDisposable.Dispose / IAsyncDisposable.DisposeAsync |
| xUnit | コンストラクター | IDisposable.Dispose / IAsyncDisposable.DisposeAsync |
| NUnit | [SetUp] | [TearDown] |
| TUnit | [Before(Test)] またはコンストラクター | [After(Test)] または IDisposable.Dispose / IAsyncDisposable.DisposeAsync |
| MSTest(クラス) | [ClassInitialize] | [ClassCleanup] |
| NUnit(クラス) | [OneTimeSetUp] | [OneTimeTearDown] |
| xUnit(クラス) | IClassFixture<T> | フィクスチャの Dispose |
| TUnit(クラス) | [Before(Class)] | [After(Class)] |
| TUnit(アセンブリ) | [Before(Assembly)] | [After(Assembly)] |
| TUnit(セッション) | [Before(TestSession)] | [After(TestSession)] |
TUnit 固有: [BeforeEvery(Test)] / [AfterEvery(Test)](および Class / Assembly の各バリアント)は、テスト実行全体を通じて、各テスト/クラス/アセンブリごとに実行されます。グローバルな横断的フックに便利です。フックには、コンテキストオブジェクト(TestContext、ClassHookContext など)や CancellationToken を任意で渡すことができます。