[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6DZKT2Y656CED5D121XECEY":3,"skill-same:01M6DZKT2Y656CED5D121XECEY":82},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":18,"publishedAt":46,"linkOnly":47,"markdownContent":48,"markdownBody":49,"frontMatter":50,"refName":52,"commitSha":53,"skillDirectory":54,"forksCount":55,"repositoryHtmlUrl":56,"repositoryHomepageUrl":57,"ownerHtmlUrl":58,"ownerBlogUrl":59,"ownerDescription":60,"updatedAt":61,"install":62,"resources":75},"01M6DZKT2Y656CED5D121XECEY","migrate-nunit-to-mstest",null,"Convert .NET tests from NUnit 3\u002F4 to MSTest v4 while preserving VSTest or MTP. Use for replacing NUnit\u002FNUnit3TestAdapter packages, Test\u002FTestCase\u002F TestCaseSource\u002FValues attributes, constraint assertions, SetUp\u002FTearDown, OneTimeSetUp, SetUpFixture, FixtureLifeCycle, TestContext, categories, retries, timeouts, and NUnit parallelization. Also use when a \"convert NUnit to MSTest\" request may already be migrated: inspect and report the no-op. Do not use for NUnit version upgrades, xUnit\u002FTUnit conversion, MSTest upgrades, or runner-only VSTest-to-MTP migration.","managedcode\u002Fdotnet-skills","managedcode","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F78253589?v=4","01M6DYDJR3EG8KB5G4PC65XCTT","Managed Code","01M6DYDJX0ZT432N04NYJW89DN",514,"MIT","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet-test-migration\u002Fskills\u002Fmigrate-nunit-to-mstest\u002FSKILL.md",[],[19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45],"mstest","migrate","nunit","migrate nunit to mstest","when","test","request","for","inspect","attributes","categories","retries","tunit","tests","teardown","runner","do","conversion","only","version","use","upgrades","parallelization","xunit","convert","report","packages","2026-11-10 10:12:30",false,"---\nname: migrate-nunit-to-mstest\ndescription: >\n  Convert .NET tests from NUnit 3\u002F4 to MSTest v4 while preserving VSTest or\n  MTP. Use for replacing NUnit\u002FNUnit3TestAdapter packages, Test\u002FTestCase\u002F\n  TestCaseSource\u002FValues attributes, constraint assertions, SetUp\u002FTearDown,\n  OneTimeSetUp, SetUpFixture, FixtureLifeCycle, TestContext, categories,\n  retries, timeouts, and NUnit parallelization. Also use when a \"convert NUnit\n  to MSTest\" request may already be migrated: inspect and report the no-op. Do\n  not use for NUnit version upgrades, xUnit\u002FTUnit conversion, MSTest upgrades,\n  or runner-only VSTest-to-MTP migration.\nlicense: MIT\n---\n\n# NUnit -> MSTest Migration\n\nConvert NUnit 3 or 4 tests to MSTest v4 without changing the target framework or test platform. A successful migration builds, discovers the same test cases, and preserves pass\u002Ffail, lifecycle, data, filtering, and concurrency semantics.\n\n## Scope\n\nUse this skill only when the project contains NUnit packages or source and the user wants MSTest. If the project already uses MSTest and contains no NUnit tests, report that no framework migration is needed and make no changes.\n\nDo not combine this framework conversion with a target-framework upgrade or VSTest\u002FMTP migration. Complete and verify one migration before starting another.\n\n## Workspace Contract\n\n- Search the current working directory for projects and source; never look for user files under this skill's directory.\n- Classify the deliverable: \"convert this project\" means edit, build, and test; \"give me a plan\" means answer without editing.\n- Preserve the user's requested project scope. Shared props may be changed only when they own NUnit package or runner configuration for that scope.\n- The final response must state the source NUnit version, preserved runner, changed files, high-risk semantic mappings, and actual test counts.\n\n## Response Mode\n\n- **Full migration request:** inspect, edit, build, and run tests. Do not stop after a plan.\n- **Focused compile error or API question:** apply only the relevant mapping.\n- **Unsupported target framework:** stop before changing packages. MSTest v4 requires .NET 8+ or .NET Framework 4.6.2+ for test applications; offer a separately approved TFM upgrade or MSTest v3.\n\n## Decisions That Change the Result\n\n| Detected state | Required action |\n|---|---|\n| No NUnit package, namespace, attribute, or constraint remains | Stop, make no changes, and run the existing test command once to prove the already-MSTest project is healthy. |\n| Source uses VSTest | Preserve VSTest. Retain and update an explicit `Microsoft.NET.Test.Sdk` pin when the repository owns one; do not introduce MTP properties. |\n| Source uses MTP | Replace NUnit-specific MTP configuration with MSTest MTP configuration. Prefer `MSTest.Sdk`; with the metapackage set `EnableMSTestRunner=true` and `OutputType=Exe`. |\n| NUnit uses its default fixture lifecycle | NUnit normally shares one fixture instance across all test cases; MSTest creates a new class instance per test. Move state that must remain class-shared behind static fields initialized by `[ClassInitialize]`, or prove the state is per-test before leaving it as an instance field. |\n| NUnit has no `Parallelizable` configuration | Preserve serial execution. Do not add `[assembly: Parallelize]`; NUnit and MSTest are both serial by default. |\n| NUnit explicitly enables parallel execution | Translate the effective scope and worker count. Never infer method-level parallelism from fixture-level settings. |\n\nFor detailed mappings, search [`references\u002Fmapping-cheatsheet.md`](references\u002Fmapping-cheatsheet.md) for constructs present in the project and read only those sections.\n\n## Fast Path\n\nFor a routine migration, converge in four phases: one batched discovery pass, one edit pass, one `dotnet test`, and one concise result.\n\n- Use an existing CI\u002Ftest result as the baseline when available.\n- Run a pre-edit baseline when counts are unavailable and the project contains parameterized tests, fixture state, namespace setup, retries, custom attributes, or explicit parallelization.\n- Do not run separate restore, build, and test commands when `dotnet test` is sufficient.\n- Do not delete NUnit runner configuration until its behavior has been translated.\n\n## Workflow\n\n### 1. Establish the baseline\n\n1. Batch-read test projects, central package files, `global.json`, `.runsettings`, `testconfig.json`, and NUnit configuration.\n2. Detect NUnit from `NUnit`, `NUnit3TestAdapter`, `NUnit.Analyzers`, `NUnit.Framework`, `NUnit.Framework.Legacy`, and `using NUnit.Framework`.\n3. State whether the source is NUnit 3 or 4 from the resolved package version.\n4. Detect VSTest or MTP and preserve it. Use `platform-detection` only when ambiguous.\n5. Record target frameworks and stop if MSTest v4 does not support them.\n6. Inventory high-risk constructs:\n   - `[TestFixture(...)]`, `[TestFixtureSource]`, `[FixtureLifeCycle]`, constructors with parameters\n   - `[TestCase]`, `[TestCaseSource]`, `TestCaseData`, `[Values]`, `[Range]`, `[Random]`, `[Sequential]`, `[Pairwise]`, custom data attributes\n   - `[Theory]`, `[Datapoint]`, `[DatapointSource]`, automatic bool\u002Fenum datapoints, `Assume.That`\n   - `[OneTimeSetUp]`, `[OneTimeTearDown]`, `[SetUpFixture]`, inheritance-based setup\n   - `Assert.That`, `Assert.Multiple`, `Assert.Throws`, `Assert.Catch`, collection constraints\n   - `[Parallelizable]`, `[NonParallelizable]`, `[LevelOfParallelism]`, `[Order]`, `[SingleThreaded]`\n   - `[Apartment]`, `[RequiresThread]`, `[CancelAfter]`, `[Timeout]`\n   - `[Culture]`, `[Platform]`, `[SetCulture]`, `[SetUICulture]`\n   - `[Explicit]`, `[Repeat]`, `[Retry]`, `[MaxTime]`, categories, properties, `[TestOf]`\n   - `[DefaultFloatingPointTolerance]`, `[NonTestAssembly]`, and deprecated fixture lifecycle aliases\n\n### 2. Replace packages without switching runners\n\nRemove NUnit-specific packages being replaced, including `NUnit`, `NUnit3TestAdapter`, `NUnit.Analyzers`, NUnit console runner packages, and NUnit-specific MTP adapters.\n\nDefault to the current stable MSTest v4 metapackage resolved from the configured package source:\n\n```xml\n\u003CPackageReference Include=\"MSTest\" Version=\"4.4.0\" \u002F>\n```\n\nThe pin is illustrative for the current release; during a real migration resolve and pin the current stable version. Preserve an explicit `Microsoft.NET.Test.Sdk` dependency for VSTest when the source project owns one. When preserving MTP, prefer `MSTest.Sdk`; otherwise use `EnableMSTestRunner=true` and `OutputType=Exe`.\n\nDo not change `TargetFramework`. Remove NUnit `.runsettings` adapter settings only after translating relevant behavior.\n\n### 3. Perform the mechanical conversion\n\n| NUnit | MSTest |\n|---|---|\n| `[TestFixture]` or fixture with tests | `[TestClass]` |\n| `[Test]` | `[TestMethod]` |\n| `[TestCase(...)]` | `[TestMethod]` + `[DataRow(...)]` |\n| `[TestCaseSource(nameof(Cases))]` | `[TestMethod]` + `[DynamicData(nameof(Cases))]` |\n| `[SetUp]` \u002F `[TearDown]` | `[TestInitialize]` \u002F `[TestCleanup]` |\n| `[OneTimeSetUp]` \u002F `[OneTimeTearDown]` | static `[ClassInitialize]` \u002F `[ClassCleanup]` |\n| `[Category(value)]` | `[TestCategory(value)]` |\n| `[Property(key, value)]` | `[TestProperty(key, value)]` |\n| `[Ignore(\"reason\")]` | `[Ignore(\"reason\")]` plus `[TestMethod]` |\n| `[Timeout(ms)]` | `[Timeout(ms)]` plus `[TestMethod]` |\n| `[Retry(n)]` | `[Retry(n - 1)]` plus `[TestMethod]` when `n > 1`; remove it when `n = 1` |\n\nRemove `using NUnit.Framework;` and `using NUnit.Framework.Legacy;`. Add `using Microsoft.VisualStudio.TestTools.UnitTesting;` when using the metapackage; `MSTest.Sdk` supplies an implicit global using.\n\nDo not mechanically seal classes or flatten inherited setup methods.\n\n### 4. Resolve semantic mappings\n\nLoad the mapping cheatsheet for every high-risk construct found in Step 1. These rules are mandatory:\n\n- NUnit's default `LifeCycle.SingleInstance` differs from MSTest's per-test class instances. Preserve class-shared fields explicitly.\n- `[OneTimeSetUp]` and `[OneTimeTearDown]` become static MSTest methods. Move any instance state they use to a static holder rather than merely adding `static`.\n- NUnit `Assert.Throws\u003CT>` and `Throws.TypeOf\u003CT>` require an exact exception type and map to `Assert.ThrowsExactly\u003CT>`. NUnit `Assert.Catch\u003CT>` and `Throws.InstanceOf\u003CT>` permit derived types and map to `Assert.Throws\u003CT>`.\n- NUnit `Is.TypeOf\u003CT>` maps to `Assert.IsExactInstanceOfType\u003CT>`; `Is.InstanceOf\u003CT>` maps to `Assert.IsInstanceOfType\u003CT>`.\n- NUnit equality constraints compare sequences element-by-element. Use `Assert.AreSequenceEqual` on MSTest 4.3+ or `CollectionAssert.AreEqual` with materialized lists; never use reference-based `Assert.AreEqual` for sequences.\n- `TestCase(ExpectedResult=...)` and `TestCaseData.Returns(...)` require rewriting the target test to assert the expected result.\n- Preserve per-row names, categories, and ignores by returning `TestDataRow\u003CT>` from `DynamicData` when needed. NUnit row properties have no direct `TestDataRow\u003CT>` equivalent and require a custom data source or an explicit metadata decision.\n- MSTest 4.4 combinatorial attributes can map NUnit `[Combinatorial]`, `[Values]`, `[Range]`, and `[Random]`; add `using Microsoft.VisualStudio.TestTools.UnitTesting.Combinatorial;`. Constructor semantics differ, so translate NUnit ranges and random bounds explicitly. `[Sequential]` still requires explicit rows or a custom data source.\n- NUnit `[Pairwise]` has no MSTest built-in. Preserve the exact generated rows in `DynamicData` or use a verified pairwise `ITestDataSource`; full Cartesian combinations change test counts and execution cost.\n- Convert `[Theory]`, `[Datapoint]`, and `[DatapointSource]` into explicit `DynamicData` or combinatorial sources. Preserve automatic bool\u002Fenum values and the rule that a theory fails when every row violates its assumptions.\n- `[Explicit]`, `[Repeat]`, parameterized fixtures, fixture sources, and `Assert.Multiple` have no behavior-identical mechanical mapping. Rewrite deliberately or report manual follow-up; never approximate silently.\n- NUnit `[Retry(n)]` counts the initial attempt, while MSTest `[Retry(n)]` counts retries after the initial attempt. Subtract one and review NUnit `RetryExceptions` filters separately.\n- `[CancelAfter(ms)]` maps to `[Timeout(ms, CooperativeCancellation = true)]`. Under VSTest, remove NUnit's injected `CancellationToken` parameter and use an injected or property-based `TestContext.CancellationToken`; VSTest otherwise treats it as missing data. Retain a method token only when the preserved runner is proven to support injection. Expand fixture-level defaults to every affected test and lifecycle method.\n- `[Apartment(ApartmentState.STA)]` maps to `[STATestClass]` or `[STATestMethod]`. Set `UseSTASynchronizationContext = true` when async continuations must remain on the STA thread. MTA is the MSTest default. `[RequiresThread]` and `[SingleThreaded]` guarantee thread identity that MSTest attributes do not generally preserve; use a custom executor or report manual follow-up.\n- Map NUnit platform and culture gates to `OSCondition`, `ArchitectureCondition`, or a `MemberCondition` helper. Map `SetCulture` and `SetUICulture` to setup\u002Fcleanup that saves and restores the original culture.\n- Expand `[DefaultFloatingPointTolerance]` into explicit deltas on every affected assertion. Preserve method-over-fixture-over-assembly precedence.\n\n### 5. Preserve lifecycle and namespace setup\n\n- `[SetUp]` and `[TearDown]` remain per-test lifecycle methods.\n- For NUnit's default single fixture instance, audit every mutable instance field. If tests depend on sharing, use static state created by `[ClassInitialize]` and released by `[ClassCleanup]`.\n- `[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]` already matches MSTest class instantiation; remove the attribute and keep per-test instance state.\n- `[SetUpFixture]` is namespace-scoped in NUnit. MSTest assembly initialization is assembly-wide. Use `[AssemblyInitialize]` only when the NUnit setup already covers the whole assembly; otherwise move setup into the affected classes or a shared helper without widening scope.\n- Preserve setup\u002Fcleanup inheritance order. Do not merge base and derived methods unless the resulting order is proven equivalent.\n- Map NUnit `TestContext.WriteLine` to an injected or property-based MSTest `TestContext.WriteLine`. Translate directory, test-name, and attachment APIs individually.\n\n### 6. Preserve parallelization and ordering\n\nNUnit and MSTest both run serially by default. Do not add parallelization for an unconfigured NUnit project.\n\nWhen NUnit explicitly opts in:\n\n- assembly `[Parallelizable(ParallelScope.Fixtures)]` -> `[assembly: Parallelize(Workers = N, Scope = ExecutionScope.ClassLevel)]`\n- fixture `[Parallelizable(ParallelScope.Children)]` or method-level parallelism -> method-level MSTest parallelization only after confirming shared instance state is safe\n- `[NonParallelizable]` -> `[DoNotParallelize]` when parallelization is enabled\n- `[LevelOfParallelism(N)]` -> `Workers = N`\n- resource-specific serialization can use MSTest 4.4 `[ResourceLock(\"key\")]` when it preserves a narrower lock than `[DoNotParallelize]`\n\nNUnit `[Order]` is not general dependency semantics. Use MSTest 4.4 `[DependsOn]` only when the source truly expresses a prerequisite; otherwise remove ordering by making tests independent or report manual follow-up.\n\n### 7. Verify parity\n\n1. Run tests with the same platform, filter, and configuration used for the baseline.\n2. Compare discovered, passed, failed, and skipped counts.\n3. Investigate every difference:\n   - missing rows -> `DataRow`, `DynamicData`, combinatorial, or row metadata conversion\n   - changed exceptions\u002Ftypes -> exact-vs-derived mapping\n   - state failures -> NUnit single-instance fixture semantics or setup order\n   - concurrency failures -> `Parallelize`, `DoNotParallelize`, worker count, or resource locks\n   - changed skips -> `Ignore`, `Explicit`, row-level ignore, or retry behavior\n4. Confirm no NUnit package, namespace, attribute, constraint, adapter setting, or custom NUnit extension remains unless documented for follow-up.\n5. Read back high-risk changed files and name the exact target APIs in the result.\n\nUse this final response shape:\n\n- **Changed:** files and exact high-risk mappings.\n- **Verified:** final command and discovered\u002Fpassed\u002Ffailed\u002Fskipped counts.\n- **Preserved:** target framework, test platform, fixture scope, and concurrency choice.\n- **Remaining:** manual follow-up, or none.\n\n## Completion Criteria\n\n- NUnit version and test platform were identified\n- NUnit packages and source constructs were converted\n- Target framework and test platform stayed unchanged\n- Default single-instance fixture semantics were audited and preserved where observable\n- Data-row metadata and assertion semantics were preserved\n- Namespace setup and parallelization scope were not silently widened\n- Every attribute on NUnit's official attribute index was classified as direct, rewritten, removed, or manual\n- Build succeeds and test results match the baseline\n- Unsupported custom extensions or behavior are called out\n\n## Follow-up\n\nRun `migrate-vstest-to-mtp` separately if the user also wants MTP. Use `writing-mstest-tests` only after parity is established.\n","# NUnit -> MSTest Migration\n\nConvert NUnit 3 or 4 tests to MSTest v4 without changing the target framework or test platform. A successful migration builds, discovers the same test cases, and preserves pass\u002Ffail, lifecycle, data, filtering, and concurrency semantics.\n\n## Scope\n\nUse this skill only when the project contains NUnit packages or source and the user wants MSTest. If the project already uses MSTest and contains no NUnit tests, report that no framework migration is needed and make no changes.\n\nDo not combine this framework conversion with a target-framework upgrade or VSTest\u002FMTP migration. Complete and verify one migration before starting another.\n\n## Workspace Contract\n\n- Search the current working directory for projects and source; never look for user files under this skill's directory.\n- Classify the deliverable: \"convert this project\" means edit, build, and test; \"give me a plan\" means answer without editing.\n- Preserve the user's requested project scope. Shared props may be changed only when they own NUnit package or runner configuration for that scope.\n- The final response must state the source NUnit version, preserved runner, changed files, high-risk semantic mappings, and actual test counts.\n\n## Response Mode\n\n- **Full migration request:** inspect, edit, build, and run tests. Do not stop after a plan.\n- **Focused compile error or API question:** apply only the relevant mapping.\n- **Unsupported target framework:** stop before changing packages. MSTest v4 requires .NET 8+ or .NET Framework 4.6.2+ for test applications; offer a separately approved TFM upgrade or MSTest v3.\n\n## Decisions That Change the Result\n\n| Detected state | Required action |\n|---|---|\n| No NUnit package, namespace, attribute, or constraint remains | Stop, make no changes, and run the existing test command once to prove the already-MSTest project is healthy. |\n| Source uses VSTest | Preserve VSTest. Retain and update an explicit `Microsoft.NET.Test.Sdk` pin when the repository owns one; do not introduce MTP properties. |\n| Source uses MTP | Replace NUnit-specific MTP configuration with MSTest MTP configuration. Prefer `MSTest.Sdk`; with the metapackage set `EnableMSTestRunner=true` and `OutputType=Exe`. |\n| NUnit uses its default fixture lifecycle | NUnit normally shares one fixture instance across all test cases; MSTest creates a new class instance per test. Move state that must remain class-shared behind static fields initialized by `[ClassInitialize]`, or prove the state is per-test before leaving it as an instance field. |\n| NUnit has no `Parallelizable` configuration | Preserve serial execution. Do not add `[assembly: Parallelize]`; NUnit and MSTest are both serial by default. |\n| NUnit explicitly enables parallel execution | Translate the effective scope and worker count. Never infer method-level parallelism from fixture-level settings. |\n\nFor detailed mappings, search [`references\u002Fmapping-cheatsheet.md`](references\u002Fmapping-cheatsheet.md) for constructs present in the project and read only those sections.\n\n## Fast Path\n\nFor a routine migration, converge in four phases: one batched discovery pass, one edit pass, one `dotnet test`, and one concise result.\n\n- Use an existing CI\u002Ftest result as the baseline when available.\n- Run a pre-edit baseline when counts are unavailable and the project contains parameterized tests, fixture state, namespace setup, retries, custom attributes, or explicit parallelization.\n- Do not run separate restore, build, and test commands when `dotnet test` is sufficient.\n- Do not delete NUnit runner configuration until its behavior has been translated.\n\n## Workflow\n\n### 1. Establish the baseline\n\n1. Batch-read test projects, central package files, `global.json`, `.runsettings`, `testconfig.json`, and NUnit configuration.\n2. Detect NUnit from `NUnit`, `NUnit3TestAdapter`, `NUnit.Analyzers`, `NUnit.Framework`, `NUnit.Framework.Legacy`, and `using NUnit.Framework`.\n3. State whether the source is NUnit 3 or 4 from the resolved package version.\n4. Detect VSTest or MTP and preserve it. Use `platform-detection` only when ambiguous.\n5. Record target frameworks and stop if MSTest v4 does not support them.\n6. Inventory high-risk constructs:\n   - `[TestFixture(...)]`, `[TestFixtureSource]`, `[FixtureLifeCycle]`, constructors with parameters\n   - `[TestCase]`, `[TestCaseSource]`, `TestCaseData`, `[Values]`, `[Range]`, `[Random]`, `[Sequential]`, `[Pairwise]`, custom data attributes\n   - `[Theory]`, `[Datapoint]`, `[DatapointSource]`, automatic bool\u002Fenum datapoints, `Assume.That`\n   - `[OneTimeSetUp]`, `[OneTimeTearDown]`, `[SetUpFixture]`, inheritance-based setup\n   - `Assert.That`, `Assert.Multiple`, `Assert.Throws`, `Assert.Catch`, collection constraints\n   - `[Parallelizable]`, `[NonParallelizable]`, `[LevelOfParallelism]`, `[Order]`, `[SingleThreaded]`\n   - `[Apartment]`, `[RequiresThread]`, `[CancelAfter]`, `[Timeout]`\n   - `[Culture]`, `[Platform]`, `[SetCulture]`, `[SetUICulture]`\n   - `[Explicit]`, `[Repeat]`, `[Retry]`, `[MaxTime]`, categories, properties, `[TestOf]`\n   - `[DefaultFloatingPointTolerance]`, `[NonTestAssembly]`, and deprecated fixture lifecycle aliases\n\n### 2. Replace packages without switching runners\n\nRemove NUnit-specific packages being replaced, including `NUnit`, `NUnit3TestAdapter`, `NUnit.Analyzers`, NUnit console runner packages, and NUnit-specific MTP adapters.\n\nDefault to the current stable MSTest v4 metapackage resolved from the configured package source:\n\n```xml\n\u003CPackageReference Include=\"MSTest\" Version=\"4.4.0\" \u002F>\n```\n\nThe pin is illustrative for the current release; during a real migration resolve and pin the current stable version. Preserve an explicit `Microsoft.NET.Test.Sdk` dependency for VSTest when the source project owns one. When preserving MTP, prefer `MSTest.Sdk`; otherwise use `EnableMSTestRunner=true` and `OutputType=Exe`.\n\nDo not change `TargetFramework`. Remove NUnit `.runsettings` adapter settings only after translating relevant behavior.\n\n### 3. Perform the mechanical conversion\n\n| NUnit | MSTest |\n|---|---|\n| `[TestFixture]` or fixture with tests | `[TestClass]` |\n| `[Test]` | `[TestMethod]` |\n| `[TestCase(...)]` | `[TestMethod]` + `[DataRow(...)]` |\n| `[TestCaseSource(nameof(Cases))]` | `[TestMethod]` + `[DynamicData(nameof(Cases))]` |\n| `[SetUp]` \u002F `[TearDown]` | `[TestInitialize]` \u002F `[TestCleanup]` |\n| `[OneTimeSetUp]` \u002F `[OneTimeTearDown]` | static `[ClassInitialize]` \u002F `[ClassCleanup]` |\n| `[Category(value)]` | `[TestCategory(value)]` |\n| `[Property(key, value)]` | `[TestProperty(key, value)]` |\n| `[Ignore(\"reason\")]` | `[Ignore(\"reason\")]` plus `[TestMethod]` |\n| `[Timeout(ms)]` | `[Timeout(ms)]` plus `[TestMethod]` |\n| `[Retry(n)]` | `[Retry(n - 1)]` plus `[TestMethod]` when `n > 1`; remove it when `n = 1` |\n\nRemove `using NUnit.Framework;` and `using NUnit.Framework.Legacy;`. Add `using Microsoft.VisualStudio.TestTools.UnitTesting;` when using the metapackage; `MSTest.Sdk` supplies an implicit global using.\n\nDo not mechanically seal classes or flatten inherited setup methods.\n\n### 4. Resolve semantic mappings\n\nLoad the mapping cheatsheet for every high-risk construct found in Step 1. These rules are mandatory:\n\n- NUnit's default `LifeCycle.SingleInstance` differs from MSTest's per-test class instances. Preserve class-shared fields explicitly.\n- `[OneTimeSetUp]` and `[OneTimeTearDown]` become static MSTest methods. Move any instance state they use to a static holder rather than merely adding `static`.\n- NUnit `Assert.Throws\u003CT>` and `Throws.TypeOf\u003CT>` require an exact exception type and map to `Assert.ThrowsExactly\u003CT>`. NUnit `Assert.Catch\u003CT>` and `Throws.InstanceOf\u003CT>` permit derived types and map to `Assert.Throws\u003CT>`.\n- NUnit `Is.TypeOf\u003CT>` maps to `Assert.IsExactInstanceOfType\u003CT>`; `Is.InstanceOf\u003CT>` maps to `Assert.IsInstanceOfType\u003CT>`.\n- NUnit equality constraints compare sequences element-by-element. Use `Assert.AreSequenceEqual` on MSTest 4.3+ or `CollectionAssert.AreEqual` with materialized lists; never use reference-based `Assert.AreEqual` for sequences.\n- `TestCase(ExpectedResult=...)` and `TestCaseData.Returns(...)` require rewriting the target test to assert the expected result.\n- Preserve per-row names, categories, and ignores by returning `TestDataRow\u003CT>` from `DynamicData` when needed. NUnit row properties have no direct `TestDataRow\u003CT>` equivalent and require a custom data source or an explicit metadata decision.\n- MSTest 4.4 combinatorial attributes can map NUnit `[Combinatorial]`, `[Values]`, `[Range]`, and `[Random]`; add `using Microsoft.VisualStudio.TestTools.UnitTesting.Combinatorial;`. Constructor semantics differ, so translate NUnit ranges and random bounds explicitly. `[Sequential]` still requires explicit rows or a custom data source.\n- NUnit `[Pairwise]` has no MSTest built-in. Preserve the exact generated rows in `DynamicData` or use a verified pairwise `ITestDataSource`; full Cartesian combinations change test counts and execution cost.\n- Convert `[Theory]`, `[Datapoint]`, and `[DatapointSource]` into explicit `DynamicData` or combinatorial sources. Preserve automatic bool\u002Fenum values and the rule that a theory fails when every row violates its assumptions.\n- `[Explicit]`, `[Repeat]`, parameterized fixtures, fixture sources, and `Assert.Multiple` have no behavior-identical mechanical mapping. Rewrite deliberately or report manual follow-up; never approximate silently.\n- NUnit `[Retry(n)]` counts the initial attempt, while MSTest `[Retry(n)]` counts retries after the initial attempt. Subtract one and review NUnit `RetryExceptions` filters separately.\n- `[CancelAfter(ms)]` maps to `[Timeout(ms, CooperativeCancellation = true)]`. Under VSTest, remove NUnit's injected `CancellationToken` parameter and use an injected or property-based `TestContext.CancellationToken`; VSTest otherwise treats it as missing data. Retain a method token only when the preserved runner is proven to support injection. Expand fixture-level defaults to every affected test and lifecycle method.\n- `[Apartment(ApartmentState.STA)]` maps to `[STATestClass]` or `[STATestMethod]`. Set `UseSTASynchronizationContext = true` when async continuations must remain on the STA thread. MTA is the MSTest default. `[RequiresThread]` and `[SingleThreaded]` guarantee thread identity that MSTest attributes do not generally preserve; use a custom executor or report manual follow-up.\n- Map NUnit platform and culture gates to `OSCondition`, `ArchitectureCondition`, or a `MemberCondition` helper. Map `SetCulture` and `SetUICulture` to setup\u002Fcleanup that saves and restores the original culture.\n- Expand `[DefaultFloatingPointTolerance]` into explicit deltas on every affected assertion. Preserve method-over-fixture-over-assembly precedence.\n\n### 5. Preserve lifecycle and namespace setup\n\n- `[SetUp]` and `[TearDown]` remain per-test lifecycle methods.\n- For NUnit's default single fixture instance, audit every mutable instance field. If tests depend on sharing, use static state created by `[ClassInitialize]` and released by `[ClassCleanup]`.\n- `[FixtureLifeCycle(LifeCycle.InstancePerTestCase)]` already matches MSTest class instantiation; remove the attribute and keep per-test instance state.\n- `[SetUpFixture]` is namespace-scoped in NUnit. MSTest assembly initialization is assembly-wide. Use `[AssemblyInitialize]` only when the NUnit setup already covers the whole assembly; otherwise move setup into the affected classes or a shared helper without widening scope.\n- Preserve setup\u002Fcleanup inheritance order. Do not merge base and derived methods unless the resulting order is proven equivalent.\n- Map NUnit `TestContext.WriteLine` to an injected or property-based MSTest `TestContext.WriteLine`. Translate directory, test-name, and attachment APIs individually.\n\n### 6. Preserve parallelization and ordering\n\nNUnit and MSTest both run serially by default. Do not add parallelization for an unconfigured NUnit project.\n\nWhen NUnit explicitly opts in:\n\n- assembly `[Parallelizable(ParallelScope.Fixtures)]` -> `[assembly: Parallelize(Workers = N, Scope = ExecutionScope.ClassLevel)]`\n- fixture `[Parallelizable(ParallelScope.Children)]` or method-level parallelism -> method-level MSTest parallelization only after confirming shared instance state is safe\n- `[NonParallelizable]` -> `[DoNotParallelize]` when parallelization is enabled\n- `[LevelOfParallelism(N)]` -> `Workers = N`\n- resource-specific serialization can use MSTest 4.4 `[ResourceLock(\"key\")]` when it preserves a narrower lock than `[DoNotParallelize]`\n\nNUnit `[Order]` is not general dependency semantics. Use MSTest 4.4 `[DependsOn]` only when the source truly expresses a prerequisite; otherwise remove ordering by making tests independent or report manual follow-up.\n\n### 7. Verify parity\n\n1. Run tests with the same platform, filter, and configuration used for the baseline.\n2. Compare discovered, passed, failed, and skipped counts.\n3. Investigate every difference:\n   - missing rows -> `DataRow`, `DynamicData`, combinatorial, or row metadata conversion\n   - changed exceptions\u002Ftypes -> exact-vs-derived mapping\n   - state failures -> NUnit single-instance fixture semantics or setup order\n   - concurrency failures -> `Parallelize`, `DoNotParallelize`, worker count, or resource locks\n   - changed skips -> `Ignore`, `Explicit`, row-level ignore, or retry behavior\n4. Confirm no NUnit package, namespace, attribute, constraint, adapter setting, or custom NUnit extension remains unless documented for follow-up.\n5. Read back high-risk changed files and name the exact target APIs in the result.\n\nUse this final response shape:\n\n- **Changed:** files and exact high-risk mappings.\n- **Verified:** final command and discovered\u002Fpassed\u002Ffailed\u002Fskipped counts.\n- **Preserved:** target framework, test platform, fixture scope, and concurrency choice.\n- **Remaining:** manual follow-up, or none.\n\n## Completion Criteria\n\n- NUnit version and test platform were identified\n- NUnit packages and source constructs were converted\n- Target framework and test platform stayed unchanged\n- Default single-instance fixture semantics were audited and preserved where observable\n- Data-row metadata and assertion semantics were preserved\n- Namespace setup and parallelization scope were not silently widened\n- Every attribute on NUnit's official attribute index was classified as direct, rewritten, removed, or manual\n- Build succeeds and test results match the baseline\n- Unsupported custom extensions or behavior are called out\n\n## Follow-up\n\nRun `migrate-vstest-to-mtp` separately if the user also wants MTP. Use `writing-mstest-tests` only after parity is established.\n",{"name":5,"license":15,"description":51},"Convert .NET tests from NUnit 3\u002F4 to MSTest v4 while preserving VSTest or MTP. Use for replacing NUnit\u002FNUnit3TestAdapter packages, Test\u002FTestCase\u002F TestCaseSource\u002FValues attributes, constraint assertions, SetUp\u002FTearDown, OneTimeSetUp, SetUpFixture, FixtureLifeCycle, TestContext, categories, retries, timeouts, and NUnit parallelization. Also use when a \"convert NUnit to MSTest\" request may already be migrated: inspect and report the no-op. Do not use for NUnit version upgrades, xUnit\u002FTUnit conversion, MSTest upgrades, or runner-only VSTest-to-MTP migration.\n","main","497cc9aba79a931ac6fb8d36a48cc22fe3d6d74b","external-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet-test-migration\u002Fskills\u002Fmigrate-nunit-to-mstest",41,"https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills","https:\u002F\u002Fskills.managed-code.com","https:\u002F\u002Fgithub.com\u002Fmanagedcode","https:\u002F\u002Fwww.managed-code.com","Open Source Community for .NET Developers: Reliable, Actively Maintained, and Community-Driven. Made in Ukraine 🇺🇦 with ❤️","2026-11-12 19:59:36",{"source":8,"skillName":5,"commands":63,"prompt":73,"zipAvailable":74},[64,67,70],{"tool":65,"command":66},"npx","npx skills add managedcode\u002Fdotnet-skills --skill migrate-nunit-to-mstest",{"tool":68,"command":69},"bunx","bunx skills add managedcode\u002Fdotnet-skills --skill migrate-nunit-to-mstest",{"tool":71,"command":72},"pnpm","pnpm dlx skills add managedcode\u002Fdotnet-skills --skill migrate-nunit-to-mstest","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet-test-migration\u002Fskills\u002Fmigrate-nunit-to-mstest\u002FSKILL.md",true,[76],{"path":77,"resourceType":78,"resourceTypeDesc":79,"sizeBytes":80,"sourceHtmlUrl":81},"references\u002Fmapping-cheatsheet.md","REFERENCE","参考文档",17143,"https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet-test-migration\u002Fskills\u002Fmigrate-nunit-to-mstest\u002Freferences\u002Fmapping-cheatsheet.md",[83,120,144,173,183,209],{"skillNo":84,"name":85,"nameZh":6,"description":86,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":87,"categories":88,"keywords":99,"publishedAt":46},"01M6DYQE1Y9FHAQHEDTVFQ3447","xtwitter-scraper","Use XTwitterScraper when a .NET application needs typed access to Xquik's X automation REST API for tweet search, user lookup, posting workflows, extraction jobs, monitors, webhooks, or giveaway draws. USE FOR: integrating the XTwitterScraper NuGet package; reviewing Xquik API client usage; modeling API-key configuration, pagination, binary exports, raw responses, retries, and webhook\u002Fevent flows in .NET. DO NOT USE FOR: unrelated stacks; direct browser automation; generic social-media strategy; or code that does not call Xquik. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FLibraries\u002FXTwitterScraper\u002Fskills\u002Fxtwitter-scraper\u002FSKILL.md",[89,94],{"categoryNo":90,"categoryName":91,"taxonomyType":92,"taxonomyTypeDesc":93},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",{"categoryNo":95,"categoryName":96,"taxonomyType":97,"taxonomyTypeDesc":98},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119],"xtwitter scraper","browser automation","workflows","run","code","browser","search","repository","api","Media","api client","access","edit","unrelated","webhooks","files","social","commands","modeling","webhook",{"skillNo":121,"name":122,"nameZh":6,"description":123,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":124,"categories":125,"keywords":132,"publishedAt":46},"01M6DYQ9S4PE1H6G8MCH9T21HR","sep","Use Sep for high-performance separated-value parsing and writing in .NET, including delimiter inference, explicit parser\u002Fwriter options, and low-allocation row\u002Fcolumn workflows. USE FOR: delimited data needs are performance-sensitive and allocation-aware; project needs explicit control over separator inference, escaping, trimming, and header behavior;. DO NOT USE FOR: unrelated stacks; generic tasks that do not need this specific guidance. INVOKES: inspect the repository context, edit targeted files, and run relevant build, test, lint, or validation commands when changes are made.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fcatalog\u002FLibraries\u002FSep\u002Fskills\u002Fsep\u002FSKILL.md",[126,129],{"categoryNo":127,"categoryName":128,"taxonomyType":92,"taxonomyTypeDesc":93},"01JBSEEDCAT000000000000018","写作与文案",{"categoryNo":130,"categoryName":131,"taxonomyType":97,"taxonomyTypeDesc":98},"01JBSEEDCAT000000000000036","内容创作者",[122,133,134,102,135,103,107,136,137,138,112,139,113,115,35,140,141,142,117,143],"writing","performance","tasks","data","parsing","parser","context","writer","options","project","high-performance",{"skillNo":145,"name":146,"nameZh":6,"description":147,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":148,"categories":149,"keywords":150,"publishedAt":46},"01M6DZS2DNEPXCGGF6SSCW6G5V","webgpu-threejs-tsl","Comprehensive guide for developing WebGPU-enabled Three.js applications using TSL (Three.js Shading Language). Covers WebGPU renderer setup, TSL syntax and node materials, compute shaders, post-processing effects, and WGSL integration. Use this skill when working with Three.js WebGPU, TSL shaders, node materials, or GPU compute in Three.js.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fwebgpu-claude-skill\u002Fskills\u002Fwebgpu-threejs-tsl\u002FSKILL.md",[],[151,152,153,154,155,156,157,158,159,160,161,162,39,163,164,165,166,167,168,169,26,170,171,172,23],"webgpu","threejs","webgpu threejs tsl","setup","shaders","gpu","integration","applications","js","node","syntax","guide","language","three","compute","post","processing","with","renderer","materials","wgsl","effects",{"skillNo":174,"name":175,"nameZh":6,"description":176,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":177,"categories":178,"keywords":179,"publishedAt":46},"01M6DZS0BQXW2B56A289XF1X76","roslynk","Use Roslynk for semantic inspection and edits of *.cs, *.razor and *.cshtml solution files.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Froslynk\u002Fskills\u002Froslynk\u002FSKILL.md",[],[175,39,115,180,181,26,182],"semantic","razor","inspection",{"skillNo":184,"name":185,"nameZh":6,"description":186,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":187,"categories":188,"keywords":189,"publishedAt":46},"01M6DZRW6MEJCVW80K3S4XV60K","system-text-json-net11","Imperative guidance for the System.Text.Json APIs added in .NET 11: the built-in `JsonNamingPolicy.PascalCase` naming policy, and the strongly-typed generic `JsonSerializerOptions.GetTypeInfo` and `JsonSerializerOptions.TryGetTypeInfo` metadata accessors (generic overloads that return a typed `JsonTypeInfo`). USE ONLY when the user is targeting net11.0 or later and needs PascalCase JSON property or dictionary-key names without writing a custom naming policy, a strongly-typed generic `JsonTypeInfo` instead of the non-generic `JsonTypeInfo`, or a no-throw way to probe whether a type's serialization metadata is resolved. DO NOT USE when the target is earlier than net11.0, the requested behavior uses an established pre-net11 naming policy, or the user explicitly selected another JSON library.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet11\u002Fskills\u002Fsystem-text-json-net11\u002FSKILL.md",[],[190,191,192,193,133,194,195,196,197,198,199,39,37,200,201,202,35,203,204,205,206,26,207,208,23],"system","json","text","system text json net11","policy","metadata","library","serialization","behavior","user","probe","guidance","later","type","dictionary","naming","targeting","custom","generic",{"skillNo":210,"name":211,"nameZh":6,"description":212,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":213,"categories":214,"keywords":217,"publishedAt":46},"01M6DZRRJP3S662NFSG1RSPQQ7","process-api-net11","Provides guidance on the new System.Diagnostics.Process APIs introduced in .NET 11: Process.Run, Process.RunAndCaptureText, Process.StartAndForget, Process.ReadAllText\u002FBytes\u002FLines, KillOnParentExit, InheritedHandles, and StartDetached. USE FOR: starting, orchestrating, or capturing output from external processes in applications targeting net11.0 or later. DO NOT USE FOR: applications targeting .NET 10 or earlier, or basic pre-.NET 11 Process.Start usage without new APIs.","https:\u002F\u002Fgithub.com\u002Fmanagedcode\u002Fdotnet-skills\u002Fblob\u002Fmain\u002Fexternal-sources\u002Fupstreams\u002Fdotnet-skills\u002Fplugins\u002Fdotnet11\u002Fskills\u002Fprocess-api-net11\u002FSKILL.md",[215,216],{"categoryNo":90,"categoryName":91,"taxonomyType":92,"taxonomyTypeDesc":93},{"categoryNo":95,"categoryName":96,"taxonomyType":97,"taxonomyTypeDesc":98},[108,218,219,220,103,221,158,190,39,201,202,35,222,223,224,225,206,26,226],"process","process api net11","diagnostics","start","basic","processes","output","usage","new"]