migrate-xunit-to-mstest について
xUnit.net v2またはv3のテストプロジェクトを、ターゲットフレームワークとテストプラットフォームを維持してMSTest v4へ移行するスキルです。動作の同等性やランナー設定を考慮し、ビルドとテストで検証します。
主な機能
- 01xUnit属性とアサーションの対応付け
- 02VSTestまたはMTPランナーの維持
- 03フィクスチャのライフサイクルと並列実行の対応
- 04ターゲットフレームワークの互換性確認
- 05ビルドとテストによる検証
活用シーン
- 01xUnitテストプロジェクトのMSTestへの移行
- 02フィクスチャとテストライフサイクルの移植
- 03アサーションやランナーの差異への対応
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill migrate-xunit-to-mstestインストール後、Claude Code で/migrate-xunit-to-mstestを入力して読み込まれたか確認します。
同梱ファイルreferences/mapping-cheatsheet.md
詳細
xUnit -> MSTest の移行
ターゲットフレームワークやテストプラットフォームを変更せずに、xUnit.net v2 または v3 のテストを MSTest v4 に変換します。移行が成功するとビルドが通り、同じテストが検出され、成功/失敗の結果と実行セマンティクスが維持されます。
対象範囲
プロジェクトに xUnit パッケージまたはソースが含まれており、ユーザーが MSTest を希望している場合に限り、このスキルを使用してください。プロジェクトがすでに MSTest を使用しており、xUnit のテストが含まれていない場合は、フレームワークの移行は不要であることを報告し、変更を加えないでください。
このフレームワーク変換を、ターゲットフレームワークのアップグレードや VSTest/MTP の移行と組み合わせないでください。別の移行を始める前に、1 つの移行を完了して検証してください。
ワークスペースの契約
- スキルの有効化後も作業を続けます。現在の作業ディレクトリから、ステージングされたプロジェクトとソースを検索してください。ユーザーファイルをこのスキルのベースディレクトリ内から探してはいけません。
- glob/検索で返されたパスをそのまま開いてください。見つかったパスをリーダーまたはパッチツールが受け付けない場合は、利用可能な別のツールで再試行してください。現在のワークスペースを調べ尽くす前に、ユーザーにパスを尋ねてはいけません。
- 要求された成果物に基づいて分類してください。「このプロジェクトを変換して」は編集、ビルド、テストを意味します。「計画を教えて」や「どう変換すればよいですか?」は回答を意味します。ファイルが存在する場合、実行の代わりに「ファイルを提供してください」と求めてはいけません。
- 最終回答では、移行元の xUnit バージョン、維持したランナー、変更したファイル、適用したリスクの高い各セマンティクスのマッピング、実際のテスト件数を示してください。フィクスチャのライフタイム、Owner のマッピング、キャンセル、並列化に関する説明は、説明文だけではなく、変更後のソースにも反映されている必要があります。
応答モード
- 移行全体の依頼: プロジェクトを調査し、編集してビルドし、テストを実行します。計画を提示しただけで終わらせてはいけません。
- 特定のコンパイルエラーまたは API に関する質問: 該当するコードを調査し、そのマッピングだけを適用します。ワークフロー全体を説明する必要はありません。
- サポート対象外のターゲットフレームワーク: パッケージを変更する前に停止します。MSTest v4 では、テストアプリケーションに .NET 8 以降、または .NET Framework 4.6.2 以降が必要です。別途承認を得た TFM のアップグレード、または中間ターゲットとして MSTest v3 を提案してください。
結果を左右する判断
機械的なマッピングの前に、以下を適用してください。
| 検出された状態 | 必須の対応 |
|---|---|
| xUnit パッケージ、名前空間、属性、フィクスチャが一切残っていない | 停止してください。ファイルを変更せず、移行は不要であることを報告し、すでに MSTest を使用しているプロジェクトが正常であることを確認するため、既存の dotnet test コマンドを 1 回実行してください。 |
| ソースが VSTest を使用している | 既存の VSTest プロパティ/構成を維持してください。ソースプロジェクトで Microsoft.NET.Test.Sdk が明示的に固定されている場合は、保持して更新することを優先してください。MSTest メタパッケージの推移的依存関係に意図的に依存しているリポジトリでは、その慣例を維持してもかまいません。MTP のプロパティを導入してはいけません。 |
| ソースが MTP を使用している | xUnit 固有の MTP 選択を MSTest MTP 構成に置き換えてください。MSTest.Sdk の使用を優先してください。メタパッケージを使用する場合は、EnableMSTestRunner=true と OutputType=Exe を設定してください。ネイティブとブリッジのどちらのコマンド統合であるかを維持し、<UseVSTest>true</UseVSTest> やその他の VSTest 専用構成を追加してはいけません。 |
| ソースが xUnit の既定の並列化に依存している | 現在のプロジェクトに独立して実行可能なテストクラスが 2 つ以上ある場合は、コンパイル対象の .cs ファイルに [assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)] を追加してください。明示的な並列化設定がなく、クラスが 1 つだけのプロジェクトでは、クラスレベルの並行実行を確認できないため、省略してください。CollectionBehavior または xunit.runner.json の明示的な設定は、現在のクラス数にかかわらず変換してください。完了を報告する前に変更したファイルを読み戻し、結果にそのファイル名を記載してください。 |
詳細なマッピングと例については、プロジェクトに実際に含まれている構文を references/mapping-cheatsheet.md で検索し、一致するセクションだけを読んでください。リファレンス全体を読み込んだり、その内容を丸ごと掲載したりしてはいけません。
迅速な手順
通常のプロジェクト移行では、4つのフェーズで完了します。まとめて1回の調査・検索、1回の編集、1回のdotnet test、そして簡潔な結果報告です。次のことは行わないでください。
- ディレクトリを一覧表示した後、別のツールで同じファイルを再度読み込む
- プロジェクトファイルを読み込んだスキルやその
references/ディレクトリにコピーする。これらのファイルは読み取り専用のガイダンスであり、編集作業用の領域ではありません - 復元がすでに最新だと分かっている場合を除き、
dotnet test --no-restoreを試す dotnet testで十分な場合に、復元、ビルド、テストを個別のコマンドで実行する- 成功したテストコマンドを再実行したり、変更されていないファイルを確認のために調べたりする
利用可能な場合は、既存のCI/テスト結果を同等性の基準として使用してください。件数が不明で、移行にデータ駆動テスト、フィクスチャ、スキップ、カスタム拡張、共有状態、またはソースだけでは同等性を確認できないその他の動作が含まれる場合に限り、編集前の新しいベースラインを実行してください。
ワークフロー
1. ベースラインを確立する
- 1回の調査で、テストプロジェクトに加え、
Directory.Build.props、Directory.Packages.props、global.json、ランナー設定をまとめて読み込み、以下のリスクの高い構成をソース全体から検索します。 - 検出したソースのバージョンを明記します。
xunit2.xおよび関連パッケージ -> xUnit v2xunit.v3またはxunit.v3.*-> xUnit v3
- プロジェクトとリポジトリの設定からVSTestまたはMTPを特定します。プラットフォームが曖昧な場合に限り
platform-detectionを使用し、検出したプラットフォームを維持します。 - ターゲットフレームワークを記録し、MSTest v4がそれらをサポートしていない場合は中止します。
- 迅速な手順で新しいベースラインが必要な場合は、既存のテストコマンドを1回実行し、検出数、成功数、失敗数、スキップ数を記録します。
- 編集前に、リスクの高い構成を洗い出します。
IClassFixture、ICollectionFixture、CollectionDefinition、カスタムのFactAttribute/TheoryAttribute/DataAttributeAssert.Throws、ThrowsAny、IsType、Record.Exception、イベントアサーションITestOutputHelper、TestContext.Current、IAsyncLifetimeCollectionBehavior、xunit.runner.json、共有の静的状態または外部状態
2. ランナーを切り替えずにパッケージを置き換える
プロジェクトファイルと中央パッケージファイルから、xUnitパッケージを削除します。対象には、xunit*、xunit.v3.*、xunit.runner.visualstudio、YTest.MTP.XUnit2、および置き換え対象のxUnit固有の補助パッケージが含まれます。
段階的な変換では、既定でMSTest v4メタパッケージを使用します。
<PackageReference Include="MSTest" Version="4.1.0" />
メタパッケージには、Microsoft.NET.Test.Sdk、MSTest.TestAdapter、MSTest.TestFramework、MSTest.Analyzersが含まれます。VSTestを維持する場合は、ソースプロジェクトで明示的に指定しているMicrosoft.NET.Test.Sdkのバージョン固定を残し、選択したMSTestのバージョンと互換性のあるバージョンに更新してください。これにより、ランナーとバージョンの互換性を確認しやすくなります。メタパッケージの推移的な依存関係に意図的に依存しているリポジトリでは、代わりにその規約を維持してもかまいません。上記の例のバージョン固定では、MSTest 4.1.0にMicrosoft.NET.Test.Sdk 18.0.1以降が必要です。互換性のない古いバージョンを固定すると、NU1605が発生することがあります。
MTPを維持する場合は、xUnitのUseMicrosoftTestingPlatformRunnerプロパティをMSTestプロジェクトに引き継がないでください。解決済みバージョンのMSTest.Sdkを優先してください。リポジトリの規約上、メタパッケージを使用する必要がある場合は、<EnableMSTestRunner>true</EnableMSTestRunner>と<OutputType>Exe</OutputType>を設定します。TestingPlatformDotnetTestSupport=trueを維持するのは、VSTestコマンドモードを通じてMTPアプリケーションを引き続き起動するリポジトリに限ります。ネイティブの.NET 10以降のMTPモードでは必要ありません。MSTest.Sdkを使用しながらVSTestを維持する場合は、<UseVSTest>true</UseVSTest>を設定します。
TargetFrameworkは変更しないでください。関連する設定を移行した後でのみ、xunit.runner.jsonを削除してください。
3. 機械的な変換を実行する
まず、共通の書き換えを適用します。
| xUnit | MSTest |
|---|---|
| クラス属性なし | [TestClass] |
[Fact] | [TestMethod] |
[Theory] + [InlineData] | [TestMethod] + [DataRow] |
[MemberData] | [DynamicData] |
[Fact(Skip = "...")] | [TestMethod] + [Ignore("...")] |
[Trait("Category", value)] | [TestCategory(value)] |
[Trait("Owner", value)] | [Owner(value)] |
その他の [Trait(key, value)] | [TestProperty(key, value)] |
Assert.Equal / NotEqual | Assert.AreEqual / AreNotEqual |
Assert.True / False | Assert.IsTrue / IsFalse |
Assert.Null / NotNull | Assert.IsNull / IsNotNull |
using Xunit; と using Xunit.Abstractions; を削除します。メタパッケージのオプションでは using Microsoft.VisualStudio.TestTools.UnitTesting; を追加します。MSTest.Sdk では、これが暗黙のグローバル using として提供されます。
既存のクラス継承は維持します。機械的にクラスを sealed にしないでください。
4. 意味上のマッピングを解決する
ステップ 1 で見つかったリスクの高い構文ごとに、マッピング早見表を読み込みます。以下のルールは必須です。
- xUnit の
Assert.Throws<T>は厳密な型一致を要求するため、MSTest のAssert.ThrowsExactly<T>にマッピングします。 - xUnit の
Assert.ThrowsAny<T>は派生型を許容するため、MSTest のAssert.Throws<T>にマッピングします。 - xUnit の
Assert.IsType<T>は厳密な型一致を要求するため、ジェネリックのAssert.IsExactInstanceOfType<T>にマッピングします。Assert.IsAssignableFrom<T>はジェネリックのAssert.IsInstanceOfType<T>にマッピングします。xUnit のアサーションの型付き戻り値が代入されている場合は、その代入を維持し、ジェネリックの MSTest オーバーロードを使用してください。非ジェネリックのTypeオーバーロードは使用しないでください。 - xUnit の
Assert.Equalでシーケンスを比較する場合、要素ごとの比較が行われます。MSTest 4.3 以降ではAssert.AreSequenceEqualを使用し、それ以前の v4 ではリストに具体化したうえでCollectionAssert.AreEqualを使用します。シーケンスの等価性を、参照に基づくAssert.AreEqualに置き換えてはいけません。 [Ignore]と[Timeout]は修飾子です。テストが検出されるように、[TestMethod]を維持してください。[DataRow]の値は、パラメーターの型と正確に一致させてください。TestContext.Current.CancellationTokenは、注入された MSTest のTestContext.CancellationTokenにマッピングします。CancellationToken.Noneや新しいCancellationTokenSourceに置き換えてはいけません。Ownerは予約済みの VSTest プロパティです。[Trait("Owner", value)]は[TestProperty("Owner", value)]ではなく、[Owner(value)]にマッピングします。- MSTest に同等の機能がないアサーション(
Assert.Collection、Assert.All、Assert.Equivalent、Record.Exception、イベントアサーション)には、明示的な手動書き換えが必要です。検証を置き換えずにアサーションを削除してはいけません。
インベントリから必要なマッピングが明らかな場合は、機械的な書き換えと意味上の書き換えを一度の編集で適用します。デフォルトでは中間ビルドを実行しないでください。最終検証で発生したコンパイラーエラーをもとに、未解決の変換のみを修正してください。
5. ライフサイクル、フィクスチャのスコープ、並列実行を維持する
- コンストラクターでのセットアップと、妥当な場合は
IDisposable/IAsyncDisposableを維持します。IAsyncLifetimeは[TestInitialize]/[TestCleanup]にマッピングします。 IClassFixture<T>は、テストクラスごとにフィクスチャのインスタンスが1つあり、そのクラスのすべてのメソッドで共有されることを意味します。TestContextを受け取る静的な[ClassInitialize]メソッドで一度だけ作成する静的なTフィールドにマッピングし、静的な[ClassCleanup]から一度だけ破棄します。このマッピングに[TestInitialize]/[TestCleanup]は決して使用しないでください。これらを使うと、テストメソッドごとにフィクスチャが1つ作成されるためです。ICollectionFixture<T>では、共有と直列実行の両方を維持します。各メンバークラスが使用する静的なLazy<T>ヘルパーを優先し、ソースのコレクションで並列実行が無効になっていた場合に限り[DoNotParallelize]を追加します。フィクスチャが実際にアセンブリ全体で共有される場合に限り、アセンブリ初期化を使用します。ITestOutputHelperを、コンストラクター注入またはプロパティベースの MSTestTestContextに置き換え、各_output.WriteLine(...)呼び出しを対応するTestContext.WriteLine(...)に置き換えます。最終結果では、TestContextの注入/プロパティとWriteLineのマッピングの両方を明記してください。「出力を移行した」だけでは同等性を示すには不十分です。
xUnit は既定でクラスを並列実行しますが、MSTest は直列実行します。現在のプロジェクトに、独立して実行可能なテストクラスが2つ以上ある場合は、次の方法でこの実効的な動作を維持します。
[assembly: Parallelize(Workers = 0, Scope = ExecutionScope.ClassLevel)]
明示的な xUnit の並列実行設定がなく、クラスが1つだけのプロジェクトでは、アセンブリポリシーを追加しないでください。維持すべきクラスレベルの並列実行がないためです。明示的な CollectionBehavior または xunit.runner.json の設定は、必ずすべて変換してください。xUnit をエミュレートするために ExecutionScope.MethodLevel を使用しないでください。フィクスチャのスコープまたは並列実行に関する判断を適用する前に、ソースで何が共有または直列化されていたか、ターゲットでそれをどのように維持するかを明記してください。
6. 同等性を検証する
- ベースラインに使用したものと同じプラットフォーム、フィルター、構成でテストを一度実行します。
dotnet testは既定でビルドするため、コンパイル失敗の切り分けが必要な場合に限り、別途ビルドを実行します。 - 検出、成功、失敗、スキップの各件数を比較します。
- 完了と宣言する前に、すべての差異を調査します。
- ケースが見つからない場合 -> 検出属性、
DynamicData、またはDataRowのリテラル型 - 例外の動作が変わった場合 -> 完全一致と派生型に関するアサーションのマッピング
- 共有状態に関する失敗や実行時間の大幅な変化がある場合 -> フィクスチャのスコープと並列実行
- テストが通知なくスキップされる場合 ->
[TestMethod]の欠落、またはランタイムスキップ変換の誤り
- ケースが見つからない場合 -> 検出属性、
- 手動でのフォローアップを明示的に記録している場合を除き、xUnit のパッケージ、名前空間、属性、ランナー設定、フィクスチャインターフェイスが残っていないことを確認します。
- 最後にテストが成功した後、高リスクのマッピングを実装する変更済みファイルと、ランナーまたは並列実行の設定ファイルを読み返します。結果では、ライフサイクル、データ、スキップ、出力、アサーション、フィクスチャのスコープ、並列実行の動作を実装するファイル名とターゲット API を正確に示します。
[ClassInitialize]、TestContext.WriteLine、Assert.IsExactInstanceOfType<T>など、型引数とメンバー名を維持してください。「属性を変換した」「出力を移行した」といった一般的な説明では、同等性の証明になりません。
最終応答は簡潔に、結果を中心に記載します。
- 変更点: ファイル名と、適用した高リスクのマッピングを正確に示します。
- 検証: 最終テストコマンドと、検出/成功/失敗/スキップの件数を示します。
- 維持した点: 変更していないターゲットフレームワークとテストプラットフォーム、およびフィクスチャまたは並列実行のスコープに関する判断を示します。
- 残作業: 手動でのフォローアップを示すか、「なし」と記載します。
完了条件
- 現在の xUnit バージョンとテストプラットフォームを特定している
- xUnit パッケージとソース構造を変換している
- ターゲットフレームワークとテストプラットフォームに変更がない
- 並行処理を観測できないため意図的に省略する場合も含め、フィクスチャのスコープと実効的な並列化の判断を明示している
- ビルドが成功する
- テストの検出結果と結果数がベースラインと一致する
- サポートされていないカスタム拡張ポイントを近似して実装せず、明示している
次の手順
ユーザーが MTP も希望する場合は、migrate-vstest-to-mtp を別途実行してください。同等性を確認した後、変換した MSTest コードを整える場合にのみ writing-mstest-tests を使用してください。