Claude Skill

dotnet-mstest

Write, run, or repair .NET tests that use MSTest. Use when a repo uses `MSTest.Sdk`, `MSTest`, `[TestClass]`, `[TestMethod]`, `DataRow`, or Microsoft.Testing.Platform-based MSTest execution.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-mstest-bfa4ebd.zip · 10 KB
Part of postpartum-genushyacinthus29/dotnet-skills — 80 skills

Install

skills CLI npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-mstest
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
Git git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

MSTest

Trigger On

  • the repo uses MSTest
  • you need to add, run, debug, or repair MSTest tests
  • the repo is moving between VSTest and Microsoft.Testing.Platform

Value

  • produce a concrete project delta: code, docs, config, tests, CI, or review artifact
  • reduce ambiguity through explicit planning, verification, and final validation skills
  • leave reusable project context so future tasks are faster and safer

Do Not Use For

  • xUnit projects
  • TUnit projects
  • generic test strategy with no MSTest-specific mechanics

Inputs

  • the nearest AGENTS.md
  • the test project file and package references
  • the active MSTest runner model

Quick Start

  1. Read the nearest AGENTS.md and confirm scope and constraints.
  2. Run this skill's Workflow through the Ralph Loop until outcomes are acceptable.
  3. Return the Required Result Format with concrete artifacts and verification evidence.

Workflow

  1. Detect the MSTest project style first:
    • MSTest.Sdk project SDK
    • MSTest meta-package
    • legacy package set with explicit Microsoft.NET.Test.Sdk
  2. Read the repo's real test command from AGENTS.md. If the repo has no explicit command yet, start with dotnet test PROJECT_OR_SOLUTION.
  3. Keep the runner model consistent:
    • MSTest.Sdk defaults to the MSTest runner on Microsoft.Testing.Platform
    • VSTest is opt-in with UseVSTest=true or legacy package choices
    • do not pass VSTest-only switches or assume legacy .runsettings behavior on Microsoft.Testing.Platform jobs
  4. Prefer [DataRow] or DynamicData for stable data-driven coverage. Keep test lifecycle hooks minimal and deterministic.
  5. Keep MSTest analyzers enabled and fix findings instead of muting them casually.
  6. Align coverage/reporting packages with the active runner.

Bootstrap When Missing

If MSTest is requested but not configured:

  1. Detect current framework first:
    • rg -n "MSTest\\.Sdk|PackageReference Include=\"MSTest\"|xunit|TUnit|UseVSTest|TestingPlatformDotnetTestSupport" -g '*.csproj' .
  2. If the repo currently uses xUnit or TUnit, do not auto-migrate. Return status: not_applicable unless migration is explicitly requested.
  3. For explicit MSTest adoption, add package(s) to target test project:
    • dotnet add TEST_PROJECT.csproj package MSTest
  4. Document runner model (MSTest.Sdk default MTP vs UseVSTest) in AGENTS.md.
  5. Run dotnet test TEST_PROJECT.csproj and return status: configured or status: improved.

Deliver

  • MSTest tests that match the repo's runner model
  • commands that work in local and CI runs
  • explicit guidance for VSTest versus Microsoft.Testing.Platform usage

Validate

  • the runner model is documented and consistent
  • test commands match that runner
  • data-driven tests stay deterministic
  • analyzer, coverage, and reporting packages align with the chosen runner

Ralph Loop

Use the Ralph Loop for every task, including docs, architecture, testing, and tooling work.

  1. Plan first (mandatory):
    • analyze current state
    • define target outcome, constraints, and risks
    • write a detailed execution plan
    • list final validation skills to run at the end, with order and reason
  2. Execute one planned step and produce a concrete delta.
  3. Review the result and capture findings with actionable next fixes.
  4. Apply fixes in small batches and rerun the relevant checks or review steps.
  5. Update the plan after each iteration.
  6. Repeat until outcomes are acceptable or only explicit exceptions remain.
  7. If a dependency is missing, bootstrap it or return status: not_applicable with explicit reason and fallback path.

Required Result Format

  • status: complete | clean | improved | configured | not_applicable | blocked
  • plan: concise plan and current iteration step
  • actions_taken: concrete changes made
  • validation_skills: final skills run, or skipped with reasons
  • verification: commands, checks, or review evidence summary
  • remaining: top unresolved items or none

For setup-only requests with no execution, return status: configured and exact next commands.

Load References

  • references/mstest.md
  • references/patterns.md
  • references/anti-patterns.md

Example Requests

  • "Fix our MSTest runner setup."
  • "Add an MSTest regression test."
  • "Move this MSTest project to Microsoft.Testing.Platform safely."
Files (dotnet-skills)
  • references
    • anti-patterns.md 14 KB
      # MSTest Anti-Patterns Reference
      
      ## 1. Duplicated Test Methods Instead of DataRow
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class CalculatorTests
      {
          [TestMethod]
          public void Add_TwoPositiveNumbers_ReturnsSum()
          {
              var calc = new Calculator();
              Assert.AreEqual(5, calc.Add(2, 3));
          }
      
          [TestMethod]
          public void Add_TwoNegativeNumbers_ReturnsSum()
          {
              var calc = new Calculator();
              Assert.AreEqual(-5, calc.Add(-2, -3));
          }
      
          [TestMethod]
          public void Add_PositiveAndNegative_ReturnsSum()
          {
              var calc = new Calculator();
              Assert.AreEqual(1, calc.Add(3, -2));
          }
      
          [TestMethod]
          public void Add_Zeros_ReturnsZero()
          {
              var calc = new Calculator();
              Assert.AreEqual(0, calc.Add(0, 0));
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class CalculatorTests
      {
          [TestMethod]
          [DataRow(2, 3, 5, DisplayName = "Two positive numbers")]
          [DataRow(-2, -3, -5, DisplayName = "Two negative numbers")]
          [DataRow(3, -2, 1, DisplayName = "Positive and negative")]
          [DataRow(0, 0, 0, DisplayName = "Zeros")]
          public void Add_WithVariousInputs_ReturnsExpectedSum(int a, int b, int expected)
          {
              var calc = new Calculator();
              Assert.AreEqual(expected, calc.Add(a, b));
          }
      }
      ```
      
      ## 2. Heavy Work in Lifecycle Hooks
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class IntegrationTests
      {
          private static SqlConnection _connection = null!;
          private static List<TestUser> _testUsers = null!;
      
          [ClassInitialize]
          public static void ClassInit(TestContext context)
          {
              _connection = new SqlConnection("Server=...");
              _connection.Open();
      
              // Heavy seeding operation
              _testUsers = [];
              for (var i = 0; i < 10000; i++)
              {
                  var user = new TestUser($"user{i}@test.com");
                  InsertUser(_connection, user);
                  _testUsers.Add(user);
              }
          }
      
          [TestMethod]
          public void GetUser_ReturnsUser()
          {
              // Simple test that doesn't need 10000 users
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class IntegrationTests
      {
          private SqlConnection _connection = null!;
      
          [TestInitialize]
          public void Setup()
          {
              _connection = new SqlConnection("Server=...");
              _connection.Open();
          }
      
          [TestCleanup]
          public void Teardown()
          {
              _connection.Dispose();
          }
      
          [TestMethod]
          public void GetUser_ReturnsUser()
          {
              // Create only what this test needs
              var user = new TestUser("test@example.com");
              InsertUser(_connection, user);
      
              var result = GetUserById(_connection, user.Id);
              Assert.IsNotNull(result);
          }
      }
      ```
      
      ## 3. Test Order Dependencies
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class OrderDependentTests
      {
          private static Order? _createdOrder;
      
          [TestMethod]
          [Priority(1)]
          public void Test1_CreateOrder()
          {
              _createdOrder = new Order("ORD-001");
              // Assumes this runs first
          }
      
          [TestMethod]
          [Priority(2)]
          public void Test2_UpdateOrder()
          {
              // Breaks if Test1 didn't run first
              _createdOrder!.Status = OrderStatus.Processing;
          }
      
          [TestMethod]
          [Priority(3)]
          public void Test3_DeleteOrder()
          {
              // Breaks if Test1 and Test2 didn't run
              Assert.IsNotNull(_createdOrder);
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class IndependentOrderTests
      {
          [TestMethod]
          public void CreateOrder_WithValidData_ReturnsOrder()
          {
              var order = new Order("ORD-001");
              Assert.AreEqual("ORD-001", order.Id);
          }
      
          [TestMethod]
          public void UpdateOrder_WithExistingOrder_UpdatesStatus()
          {
              var order = new Order("ORD-002"); // Each test creates its own
              order.Status = OrderStatus.Processing;
              Assert.AreEqual(OrderStatus.Processing, order.Status);
          }
      
          [TestMethod]
          public void DeleteOrder_WithExistingOrder_RemovesOrder()
          {
              var order = new Order("ORD-003");
              var repository = new InMemoryOrderRepository();
              repository.Add(order);
      
              repository.Delete(order.Id);
      
              Assert.IsNull(repository.GetById(order.Id));
          }
      }
      ```
      
      ## 4. Missing Assert Statements
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class ServiceTests
      {
          [TestMethod]
          public void ProcessData_DoesNotThrow()
          {
              var service = new DataService();
              service.ProcessData("test"); // No assertion
          }
      
          [TestMethod]
          public async Task LoadAsync_CompletesSuccessfully()
          {
              var loader = new DataLoader();
              await loader.LoadAsync(); // No assertion
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class ServiceTests
      {
          [TestMethod]
          public void ProcessData_WithValidInput_ReturnsProcessedResult()
          {
              var service = new DataService();
              var result = service.ProcessData("test");
      
              Assert.IsNotNull(result);
              Assert.AreEqual("PROCESSED: test", result);
          }
      
          [TestMethod]
          public async Task LoadAsync_WithValidSource_ReturnsData()
          {
              var loader = new DataLoader();
              var data = await loader.LoadAsync();
      
              Assert.IsNotNull(data);
              Assert.IsTrue(data.Count > 0);
          }
      }
      ```
      
      ## 5. Catching Exceptions Incorrectly
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class ExceptionTests
      {
          [TestMethod]
          public void Validate_WithNull_ThrowsException()
          {
              try
              {
                  var validator = new Validator();
                  validator.Validate(null!);
                  Assert.Fail("Expected exception was not thrown");
              }
              catch (Exception ex)
              {
                  // Too broad, catches any exception
                  Assert.IsNotNull(ex);
              }
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class ExceptionTests
      {
          [TestMethod]
          public void Validate_WithNull_ThrowsArgumentNullException()
          {
              var validator = new Validator();
      
              var exception = Assert.ThrowsException<ArgumentNullException>(
                  () => validator.Validate(null!));
      
              Assert.AreEqual("input", exception.ParamName);
          }
      
          [TestMethod]
          public async Task ValidateAsync_WithNull_ThrowsArgumentNullException()
          {
              var validator = new AsyncValidator();
      
              var exception = await Assert.ThrowsExceptionAsync<ArgumentNullException>(
                  async () => await validator.ValidateAsync(null!));
      
              Assert.AreEqual("input", exception.ParamName);
          }
      }
      ```
      
      ## 6. Shared Mutable State Between Tests
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class SharedStateTests
      {
          private static List<string> _items = []; // Static mutable state
      
          [TestMethod]
          public void AddItem_IncreasesCount()
          {
              _items.Add("item1");
              Assert.AreEqual(1, _items.Count); // May fail if other tests ran
          }
      
          [TestMethod]
          public void AddTwoItems_CountIsTwo()
          {
              _items.Add("item2");
              _items.Add("item3");
              Assert.AreEqual(2, _items.Count); // Fails because state leaked
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class IsolatedStateTests
      {
          private List<string> _items = null!;
      
          [TestInitialize]
          public void Setup()
          {
              _items = []; // Fresh instance per test
          }
      
          [TestMethod]
          public void AddItem_IncreasesCount()
          {
              _items.Add("item1");
              Assert.AreEqual(1, _items.Count);
          }
      
          [TestMethod]
          public void AddTwoItems_CountIsTwo()
          {
              _items.Add("item1");
              _items.Add("item2");
              Assert.AreEqual(2, _items.Count);
          }
      }
      ```
      
      ## 7. Ignoring Async/Await in Tests
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class AsyncTests
      {
          [TestMethod]
          public void LoadData_Completes() // Not async
          {
              var service = new DataService();
              var task = service.LoadAsync(); // Fire and forget
              // Test completes before async work finishes
          }
      
          [TestMethod]
          public void ProcessAsync_WithResult()
          {
              var service = new DataService();
              var result = service.ProcessAsync().Result; // Blocking call, can deadlock
              Assert.IsNotNull(result);
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class AsyncTests
      {
          [TestMethod]
          public async Task LoadData_CompletesSuccessfully()
          {
              var service = new DataService();
              var data = await service.LoadAsync();
              Assert.IsNotNull(data);
          }
      
          [TestMethod]
          public async Task ProcessAsync_WithValidInput_ReturnsResult()
          {
              var service = new DataService();
              var result = await service.ProcessAsync();
              Assert.IsNotNull(result);
          }
      }
      ```
      
      ## 8. Hardcoded Test Data Paths
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class FileTests
      {
          [TestMethod]
          public void ReadConfig_ParsesCorrectly()
          {
              var config = ConfigReader.Read(@"C:\Users\john\project\test-data\config.json");
              Assert.IsNotNull(config);
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class FileTests
      {
          [TestMethod]
          public void ReadConfig_ParsesCorrectly()
          {
              var testDataPath = Path.Combine(
                  AppContext.BaseDirectory,
                  "TestData",
                  "config.json");
      
              var config = ConfigReader.Read(testDataPath);
              Assert.IsNotNull(config);
          }
      
          [TestMethod]
          public void ReadConfig_WithEmbeddedResource_ParsesCorrectly()
          {
              using var stream = GetType().Assembly
                  .GetManifestResourceStream("MyTests.TestData.config.json");
              var config = ConfigReader.Read(stream!);
              Assert.IsNotNull(config);
          }
      }
      ```
      
      ## 9. Testing Implementation Details
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class ImplementationTests
      {
          [TestMethod]
          public void Cache_UsesCorrectInternalStructure()
          {
              var cache = new Cache<string>();
              cache.Add("key", "value");
      
              // Accessing private field through reflection
              var dictionary = typeof(Cache<string>)
                  .GetField("_dictionary", BindingFlags.NonPublic | BindingFlags.Instance)!
                  .GetValue(cache) as Dictionary<string, string>;
      
              Assert.AreEqual(1, dictionary!.Count);
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class BehaviorTests
      {
          [TestMethod]
          public void Cache_AfterAdd_ReturnsStoredValue()
          {
              var cache = new Cache<string>();
              cache.Add("key", "value");
      
              var result = cache.Get("key");
      
              Assert.AreEqual("value", result);
          }
      
          [TestMethod]
          public void Cache_AfterAdd_ContainsKey()
          {
              var cache = new Cache<string>();
              cache.Add("key", "value");
      
              Assert.IsTrue(cache.ContainsKey("key"));
          }
      }
      ```
      
      ## 10. Overly Strict DateTime Assertions
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class TimestampTests
      {
          [TestMethod]
          public void CreateOrder_SetsCreatedAt()
          {
              var order = new Order();
              Assert.AreEqual(DateTime.Now, order.CreatedAt); // Flaky, timing issue
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class TimestampTests
      {
          [TestMethod]
          public void CreateOrder_SetsCreatedAtToApproximatelyNow()
          {
              var before = DateTime.UtcNow;
              var order = new Order();
              var after = DateTime.UtcNow;
      
              Assert.IsTrue(order.CreatedAt >= before && order.CreatedAt <= after);
          }
      
          [TestMethod]
          public void CreateOrder_WithTimeProvider_SetsExpectedTime()
          {
              var fixedTime = new DateTime(2024, 1, 15, 10, 30, 0, DateTimeKind.Utc);
              var timeProvider = new FakeTimeProvider(fixedTime);
      
              var order = new Order(timeProvider);
      
              Assert.AreEqual(fixedTime, order.CreatedAt);
          }
      }
      ```
      
      ## 11. Missing Cleanup for External Resources
      
      ### Bad
      
      ```csharp
      [TestClass]
      public class ResourceLeakTests
      {
          [TestMethod]
          public void WriteFile_CreatesFile()
          {
              var path = Path.GetTempFileName();
              File.WriteAllText(path, "test content");
              Assert.IsTrue(File.Exists(path));
              // File never deleted, accumulates over test runs
          }
      
          [TestMethod]
          public void OpenConnection_Succeeds()
          {
              var connection = new SqlConnection("...");
              connection.Open();
              Assert.AreEqual(ConnectionState.Open, connection.State);
              // Connection never disposed
          }
      }
      ```
      
      ### Good
      
      ```csharp
      [TestClass]
      public class ProperResourceTests
      {
          private readonly List<string> _tempFiles = [];
      
          [TestCleanup]
          public void Cleanup()
          {
              foreach (var file in _tempFiles)
              {
                  if (File.Exists(file))
                  {
                      File.Delete(file);
                  }
              }
          }
      
          [TestMethod]
          public void WriteFile_CreatesFile()
          {
              var path = Path.GetTempFileName();
              _tempFiles.Add(path);
      
              File.WriteAllText(path, "test content");
      
              Assert.IsTrue(File.Exists(path));
          }
      
          [TestMethod]
          public void OpenConnection_Succeeds()
          {
              using var connection = new SqlConnection("...");
              connection.Open();
              Assert.AreEqual(ConnectionState.Open, connection.State);
          }
      }
      ```
      
      ## 12. Mixing VSTest and Microsoft.Testing.Platform Patterns
      
      ### Bad
      
      ```csharp
      // Project uses MSTest.Sdk (Microsoft.Testing.Platform by default)
      // but test code uses VSTest-specific patterns
      
      [TestClass]
      public class MixedRunnerTests
      {
          [TestMethod]
          [DeploymentItem("TestData/file.json")] // VSTest-specific, may not work
          public void Test_WithDeploymentItem()
          {
              // ...
          }
      }
      ```
      
      ### Good
      
      ```csharp
      // For MSTest.Sdk projects, use runner-agnostic patterns
      [TestClass]
      public class RunnerAgnosticTests
      {
          [TestMethod]
          public void Test_WithEmbeddedResource()
          {
              using var stream = GetType().Assembly
                  .GetManifestResourceStream("MyTests.TestData.file.json");
              // ...
          }
      
          [TestMethod]
          public void Test_WithTestDataFolder()
          {
              var path = Path.Combine(AppContext.BaseDirectory, "TestData", "file.json");
              // ...
          }
      }
      ```
      
      ## Sources
      
      - [MSTest best practices](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices)
      - [Async testing patterns](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-writing-tests#test-async-methods)
      - [Test isolation](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices#test-isolation)
      
    • mstest.md 2.8 KB
      # MSTest in MCAF
      
      ## Open/Free Status
      
      - open source
      - free to use
      
      ## Install
      
      Project template:
      
      ```bash
      dotnet new mstest
      ```
      
      Or add the current MSTest meta-package to an existing test project:
      
      ```bash
      dotnet add package MSTest
      ```
      
      ## Verify First
      
      Before adding packages, check which MSTest model the repo already uses:
      
      ```bash
      rg -n "MSTest\\.Sdk|UseVSTest|PackageReference Include=\"MSTest\"|Microsoft\\.NET\\.Test\\.Sdk|TestingPlatformDotnetTestSupport" -g '*.csproj' .
      ```
      
      Use this reference when the repository already chose MSTest and you need framework-specific commands, package checks, or CI guardrails.
      
      ## Detect the MSTest Model
      
      Use the project file as the source of truth:
      
      ```bash
      rg -n "MSTest\\.Sdk|UseVSTest|PackageReference Include=\"MSTest\"|Microsoft\\.NET\\.Test\\.Sdk|TestingPlatformDotnetTestSupport" -g '*.csproj' .
      ```
      
      Typical markers:
      
      - `MSTest.Sdk`: modern MSTest project SDK, Microsoft.Testing.Platform by default
      - `UseVSTest=true`: explicit VSTest fallback
      - `MSTest` meta-package: framework, adapter, analyzers, and runner-related packages packaged together
      
      ## Common Commands
      
      Start with the repo's `test` command from `AGENTS.md`. If the repo has not documented one yet, these are the safe defaults:
      
      ```bash
      dotnet test MySolution.sln
      dotnet test tests/MyProject.Tests/MyProject.Tests.csproj
      dotnet test tests/MyProject.Tests/MyProject.Tests.csproj --no-build
      ```
      
      Focused VSTest-style run:
      
      ```bash
      dotnet test tests/MyProject.Tests/MyProject.Tests.csproj --filter "FullyQualifiedName~Namespace.TypeName"
      ```
      
      New-project template:
      
      The current `dotnet new mstest` template can target either `VSTest` or `MSTest` as the runner, and `MSTest.Sdk` uses Microsoft.Testing.Platform by default.
      
      ## CI Notes
      
      - Document the runner choice in `AGENTS.md`.
      - If the project uses `MSTest.Sdk`, assume Microsoft.Testing.Platform unless the project opts back into VSTest.
      - Keep coverage driver aligned with the runner:
        - VSTest: `--collect:"XPlat Code Coverage"` or `coverlet.collector`
        - Microsoft.Testing.Platform: MSTest SDK coverage extension or `coverlet.MTP`
      - Keep MSTest analyzers enabled.
      
      ## Good Defaults
      
      - prefer `[DataRow]` or `DynamicData` over duplicated test methods
      - keep `ClassInitialize`, `ClassCleanup`, and similar hooks short and deterministic
      - avoid runner migrations and behavior changes in the same PR unless the current runner setup is already broken
      
      ## Sources
      
      - [Get started with MSTest](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-getting-started)
      - [MSTest overview](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-intro)
      - [MSTest SDK configuration](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-sdk)
      - [Microsoft.Testing.Platform overview](https://learn.microsoft.com/en-us/dotnet/core/testing/microsoft-testing-platform-intro)
      
    • patterns.md 11.6 KB
      # MSTest Patterns Reference
      
      ## Data-Driven Testing Patterns
      
      ### DataRow for Inline Test Data
      
      ```csharp
      [TestClass]
      public class CalculatorTests
      {
          [TestMethod]
          [DataRow(2, 3, 5)]
          [DataRow(0, 0, 0)]
          [DataRow(-1, 1, 0)]
          [DataRow(int.MaxValue, 0, int.MaxValue)]
          public void Add_WithVariousInputs_ReturnsExpectedSum(int a, int b, int expected)
          {
              var calculator = new Calculator();
              var result = calculator.Add(a, b);
              Assert.AreEqual(expected, result);
          }
      }
      ```
      
      ### DataRow with Display Names
      
      ```csharp
      [TestClass]
      public class ValidationTests
      {
          [TestMethod]
          [DataRow("", false, DisplayName = "Empty string is invalid")]
          [DataRow("  ", false, DisplayName = "Whitespace is invalid")]
          [DataRow("valid@email.com", true, DisplayName = "Valid email passes")]
          [DataRow("invalid", false, DisplayName = "Missing @ is invalid")]
          public void IsValidEmail_WithVariousInputs_ReturnsExpectedResult(string email, bool expected)
          {
              var validator = new EmailValidator();
              Assert.AreEqual(expected, validator.IsValid(email));
          }
      }
      ```
      
      ### DynamicData for Complex Test Data
      
      ```csharp
      [TestClass]
      public class OrderProcessorTests
      {
          public static IEnumerable<object[]> OrderTestData =>
          [
              [new Order { Items = [], Total = 0 }, OrderStatus.Empty],
              [new Order { Items = [new("Item1", 10)], Total = 10 }, OrderStatus.Valid],
              [new Order { Items = [new("Item1", -5)], Total = -5 }, OrderStatus.Invalid]
          ];
      
          [TestMethod]
          [DynamicData(nameof(OrderTestData))]
          public void ProcessOrder_WithVariousOrders_ReturnsCorrectStatus(Order order, OrderStatus expected)
          {
              var processor = new OrderProcessor();
              var result = processor.Process(order);
              Assert.AreEqual(expected, result);
          }
      }
      ```
      
      ### DynamicData from Method
      
      ```csharp
      [TestClass]
      public class ParserTests
      {
          public static IEnumerable<object[]> GetParseTestCases()
          {
              yield return ["123", 123];
              yield return ["456", 456];
              yield return ["-789", -789];
          }
      
          [TestMethod]
          [DynamicData(nameof(GetParseTestCases), DynamicDataSourceType.Method)]
          public void Parse_WithValidInput_ReturnsExpectedValue(string input, int expected)
          {
              var result = int.Parse(input);
              Assert.AreEqual(expected, result);
          }
      }
      ```
      
      ## Lifecycle Hook Patterns
      
      ### TestInitialize and TestCleanup
      
      ```csharp
      [TestClass]
      public class DatabaseTests
      {
          private TestDatabaseContext _context = null!;
      
          [TestInitialize]
          public void Setup()
          {
              _context = new TestDatabaseContext();
              _context.Database.EnsureCreated();
          }
      
          [TestCleanup]
          public void Teardown()
          {
              _context.Database.EnsureDeleted();
              _context.Dispose();
          }
      
          [TestMethod]
          public async Task CreateUser_WithValidData_PersistsToDatabase()
          {
              var user = new User("test@example.com", "TestUser");
              _context.Users.Add(user);
              await _context.SaveChangesAsync();
      
              Assert.AreEqual(1, await _context.Users.CountAsync());
          }
      }
      ```
      
      ### ClassInitialize and ClassCleanup
      
      ```csharp
      [TestClass]
      public class IntegrationTests
      {
          private static HttpClient _client = null!;
          private static WebApplicationFactory<Program> _factory = null!;
      
          [ClassInitialize]
          public static void ClassSetup(TestContext context)
          {
              _factory = new WebApplicationFactory<Program>();
              _client = _factory.CreateClient();
          }
      
          [ClassCleanup]
          public static void ClassTeardown()
          {
              _client.Dispose();
              _factory.Dispose();
          }
      
          [TestMethod]
          public async Task GetEndpoint_ReturnsSuccess()
          {
              var response = await _client.GetAsync("/api/health");
              Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
          }
      }
      ```
      
      ### AssemblyInitialize and AssemblyCleanup
      
      ```csharp
      [TestClass]
      public class GlobalTestSetup
      {
          [AssemblyInitialize]
          public static void AssemblySetup(TestContext context)
          {
              // One-time setup for entire test assembly
              Environment.SetEnvironmentVariable("TEST_MODE", "true");
          }
      
          [AssemblyCleanup]
          public static void AssemblyTeardown()
          {
              Environment.SetEnvironmentVariable("TEST_MODE", null);
          }
      }
      ```
      
      ## Parallel Testing Patterns
      
      ### Parallel Execution at Class Level
      
      ```csharp
      // Enable parallel execution in .runsettings or project file
      // <Parallelize Workers="4" Scope="ClassLevel" />
      
      [TestClass]
      public class IndependentTestsA
      {
          [TestMethod]
          public void TestA1() { /* ... */ }
      
          [TestMethod]
          public void TestA2() { /* ... */ }
      }
      
      [TestClass]
      public class IndependentTestsB
      {
          [TestMethod]
          public void TestB1() { /* ... */ }
      }
      ```
      
      ### Disabling Parallelism for Specific Tests
      
      ```csharp
      [TestClass]
      [DoNotParallelize]
      public class SequentialDatabaseTests
      {
          // Tests in this class run sequentially
          [TestMethod]
          public void Test1_CreateRecord() { /* ... */ }
      
          [TestMethod]
          public void Test2_UpdateRecord() { /* ... */ }
      }
      ```
      
      ### Thread-Safe Test Fixtures
      
      ```csharp
      [TestClass]
      public class ThreadSafeTests
      {
          private static readonly Lock _lock = new();
          private static int _sharedCounter;
      
          [TestMethod]
          public void IncrementCounter_ThreadSafe()
          {
              lock (_lock)
              {
                  _sharedCounter++;
                  Assert.IsTrue(_sharedCounter > 0);
              }
          }
      }
      ```
      
      ## Async Testing Patterns
      
      ### Basic Async Test
      
      ```csharp
      [TestClass]
      public class AsyncServiceTests
      {
          [TestMethod]
          public async Task GetDataAsync_WhenCalled_ReturnsData()
          {
              var service = new DataService();
              var result = await service.GetDataAsync();
              Assert.IsNotNull(result);
          }
      }
      ```
      
      ### Testing Async Exceptions
      
      ```csharp
      [TestClass]
      public class AsyncExceptionTests
      {
          [TestMethod]
          public async Task ProcessAsync_WithInvalidInput_ThrowsArgumentException()
          {
              var service = new ValidationService();
      
              await Assert.ThrowsExceptionAsync<ArgumentException>(
                  async () => await service.ProcessAsync(null!));
          }
      }
      ```
      
      ### Testing Cancellation
      
      ```csharp
      [TestClass]
      public class CancellationTests
      {
          [TestMethod]
          public async Task LongRunningOperation_WhenCancelled_ThrowsOperationCanceled()
          {
              using var cts = new CancellationTokenSource();
              var service = new LongRunningService();
      
              var task = service.ProcessAsync(cts.Token);
              await cts.CancelAsync();
      
              await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => task);
          }
      }
      ```
      
      ## Dependency Injection Patterns
      
      ### Test Class with Primary Constructor
      
      ```csharp
      [TestClass]
      public class ServiceTests
      {
          private readonly Mock<IRepository> _mockRepository = new();
          private readonly Mock<ILogger<OrderService>> _mockLogger = new();
      
          private OrderService CreateService() =>
              new(_mockRepository.Object, _mockLogger.Object);
      
          [TestMethod]
          public async Task CreateOrder_WithValidOrder_SavesAndReturnsOrder()
          {
              var order = new Order("ORD-001", 100m);
              _mockRepository.Setup(r => r.SaveAsync(order))
                  .ReturnsAsync(order);
      
              var service = CreateService();
              var result = await service.CreateOrderAsync(order);
      
              Assert.AreEqual("ORD-001", result.Id);
              _mockRepository.Verify(r => r.SaveAsync(order), Times.Once);
          }
      }
      ```
      
      ### Test Base Class with Common Setup
      
      ```csharp
      public abstract class ServiceTestBase<TService> where TService : class
      {
          protected Mock<ILogger<TService>> MockLogger { get; } = new();
          protected Mock<IConfiguration> MockConfiguration { get; } = new();
      
          [TestInitialize]
          public virtual void BaseSetup()
          {
              MockConfiguration.Setup(c => c["Environment"]).Returns("Test");
          }
      }
      
      [TestClass]
      public class UserServiceTests : ServiceTestBase<UserService>
      {
          private readonly Mock<IUserRepository> _mockRepo = new();
      
          [TestMethod]
          public async Task GetUser_ReturnsUser()
          {
              _mockRepo.Setup(r => r.GetByIdAsync(1))
                  .ReturnsAsync(new User(1, "Test"));
      
              var service = new UserService(_mockRepo.Object, MockLogger.Object);
              var user = await service.GetUserAsync(1);
      
              Assert.AreEqual("Test", user.Name);
          }
      }
      ```
      
      ## Assertion Patterns
      
      ### Collection Assertions
      
      ```csharp
      [TestClass]
      public class CollectionAssertionTests
      {
          [TestMethod]
          public void GetUsers_ReturnsExpectedCollection()
          {
              var service = new UserService();
              var users = service.GetActiveUsers();
      
              CollectionAssert.IsNotNull(users);
              CollectionAssert.AllItemsAreNotNull(users);
              CollectionAssert.AllItemsAreInstancesOfType(users, typeof(User));
              CollectionAssert.AreEqual(new[] { "Alice", "Bob" }, users.Select(u => u.Name).ToList());
          }
      }
      ```
      
      ### String Assertions
      
      ```csharp
      [TestClass]
      public class StringAssertionTests
      {
          [TestMethod]
          public void FormatMessage_ContainsExpectedParts()
          {
              var formatter = new MessageFormatter();
              var result = formatter.Format("Hello", "World");
      
              StringAssert.Contains(result, "Hello");
              StringAssert.StartsWith(result, "Message:");
              StringAssert.EndsWith(result, ".");
              StringAssert.Matches(result, new Regex(@"Message: .+ - .+\."));
          }
      }
      ```
      
      ### Custom Assert Extensions
      
      ```csharp
      public static class CustomAssert
      {
          public static void IsWithinRange<T>(T value, T min, T max) where T : IComparable<T>
          {
              if (value.CompareTo(min) < 0 || value.CompareTo(max) > 0)
              {
                  throw new AssertFailedException(
                      $"Expected {value} to be between {min} and {max}");
              }
          }
      }
      
      [TestClass]
      public class RangeTests
      {
          [TestMethod]
          public void Calculate_ReturnsValueInExpectedRange()
          {
              var calculator = new Calculator();
              var result = calculator.RandomInRange(1, 100);
              CustomAssert.IsWithinRange(result, 1, 100);
          }
      }
      ```
      
      ## Test Organization Patterns
      
      ### Arrange-Act-Assert Structure
      
      ```csharp
      [TestClass]
      public class ShoppingCartTests
      {
          [TestMethod]
          public void AddItem_WhenCartEmpty_IncreasesItemCount()
          {
              // Arrange
              var cart = new ShoppingCart();
              var item = new CartItem("SKU-001", "Widget", 9.99m);
      
              // Act
              cart.AddItem(item);
      
              // Assert
              Assert.AreEqual(1, cart.ItemCount);
              Assert.AreEqual(9.99m, cart.Total);
          }
      }
      ```
      
      ### Test Categories
      
      ```csharp
      [TestClass]
      public class MixedTests
      {
          [TestMethod]
          [TestCategory("Unit")]
          public void UnitTest_FastAndIsolated()
          {
              // Fast, no external dependencies
          }
      
          [TestMethod]
          [TestCategory("Integration")]
          public async Task IntegrationTest_UsesDatabase()
          {
              // Requires database
              await Task.CompletedTask;
          }
      
          [TestMethod]
          [TestCategory("Smoke")]
          public void SmokeTest_BasicFunctionality()
          {
              // Quick sanity check
          }
      }
      ```
      
      ### Test Priority
      
      ```csharp
      [TestClass]
      public class PrioritizedTests
      {
          [TestMethod]
          [Priority(1)]
          public void CriticalPath_Test()
          {
              // Most important, run first
          }
      
          [TestMethod]
          [Priority(2)]
          public void Secondary_Test()
          {
              // Run after priority 1
          }
      }
      ```
      
      ## Sources
      
      - [DataRow attribute](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-writing-tests#data-driven-tests)
      - [Test lifecycle attributes](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-writing-tests#test-lifecycle)
      - [Parallel test execution](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-configure-execution#parallelize-tests)
      - [Assert classes](https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-mstest-writing-tests#assertions)
      
  • SKILL.md 4.7 KB
    ---
    name: dotnet-mstest
    version: "1.0.0"
    category: "Testing"
    description: "Write, run, or repair .NET tests that use MSTest. Use when a repo uses `MSTest.Sdk`, `MSTest`, `[TestClass]`, `[TestMethod]`, `DataRow`, or Microsoft.Testing.Platform-based MSTest execution."
    compatibility: "Requires a .NET solution or project with MSTest packages; respects the repo's `AGENTS.md` commands first."
    ---
    
    # MSTest
    
    ## Trigger On
    
    - the repo uses MSTest
    - you need to add, run, debug, or repair MSTest tests
    - the repo is moving between VSTest and Microsoft.Testing.Platform
    
    ## Value
    
    - produce a concrete project delta: code, docs, config, tests, CI, or review artifact
    - reduce ambiguity through explicit planning, verification, and final validation skills
    - leave reusable project context so future tasks are faster and safer
    
    ## Do Not Use For
    
    - xUnit projects
    - TUnit projects
    - generic test strategy with no MSTest-specific mechanics
    
    ## Inputs
    
    - the nearest `AGENTS.md`
    - the test project file and package references
    - the active MSTest runner model
    
    ## Quick Start
    
    1. Read the nearest `AGENTS.md` and confirm scope and constraints.
    2. Run this skill's `Workflow` through the `Ralph Loop` until outcomes are acceptable.
    3. Return the `Required Result Format` with concrete artifacts and verification evidence.
    
    ## Workflow
    
    1. Detect the MSTest project style first:
       - `MSTest.Sdk` project SDK
       - `MSTest` meta-package
       - legacy package set with explicit `Microsoft.NET.Test.Sdk`
    2. Read the repo's real `test` command from `AGENTS.md`. If the repo has no explicit command yet, start with `dotnet test PROJECT_OR_SOLUTION`.
    3. Keep the runner model consistent:
       - `MSTest.Sdk` defaults to the MSTest runner on Microsoft.Testing.Platform
       - VSTest is opt-in with `UseVSTest=true` or legacy package choices
       - do not pass VSTest-only switches or assume legacy `.runsettings` behavior on Microsoft.Testing.Platform jobs
    4. Prefer `[DataRow]` or `DynamicData` for stable data-driven coverage. Keep test lifecycle hooks minimal and deterministic.
    5. Keep MSTest analyzers enabled and fix findings instead of muting them casually.
    6. Align coverage/reporting packages with the active runner.
    
    ## Bootstrap When Missing
    
    If MSTest is requested but not configured:
    
    1. Detect current framework first:
       - `rg -n "MSTest\\.Sdk|PackageReference Include=\"MSTest\"|xunit|TUnit|UseVSTest|TestingPlatformDotnetTestSupport" -g '*.csproj' .`
    2. If the repo currently uses `xUnit` or `TUnit`, do not auto-migrate. Return `status: not_applicable` unless migration is explicitly requested.
    3. For explicit MSTest adoption, add package(s) to target test project:
       - `dotnet add TEST_PROJECT.csproj package MSTest`
    4. Document runner model (`MSTest.Sdk` default MTP vs `UseVSTest`) in `AGENTS.md`.
    5. Run `dotnet test TEST_PROJECT.csproj` and return `status: configured` or `status: improved`.
    
    
    ## Deliver
    
    - MSTest tests that match the repo's runner model
    - commands that work in local and CI runs
    - explicit guidance for VSTest versus Microsoft.Testing.Platform usage
    
    ## Validate
    
    - the runner model is documented and consistent
    - test commands match that runner
    - data-driven tests stay deterministic
    - analyzer, coverage, and reporting packages align with the chosen runner
    
    ## Ralph Loop
    
    Use the Ralph Loop for every task, including docs, architecture, testing, and tooling work.
    
    1. Plan first (mandatory):
       - analyze current state
       - define target outcome, constraints, and risks
       - write a detailed execution plan
       - list final validation skills to run at the end, with order and reason
    2. Execute one planned step and produce a concrete delta.
    3. Review the result and capture findings with actionable next fixes.
    4. Apply fixes in small batches and rerun the relevant checks or review steps.
    5. Update the plan after each iteration.
    6. Repeat until outcomes are acceptable or only explicit exceptions remain.
    7. If a dependency is missing, bootstrap it or return `status: not_applicable` with explicit reason and fallback path.
    
    ### Required Result Format
    
    - `status`: `complete` | `clean` | `improved` | `configured` | `not_applicable` | `blocked`
    - `plan`: concise plan and current iteration step
    - `actions_taken`: concrete changes made
    - `validation_skills`: final skills run, or skipped with reasons
    - `verification`: commands, checks, or review evidence summary
    - `remaining`: top unresolved items or `none`
    
    For setup-only requests with no execution, return `status: configured` and exact next commands.
    
    ## Load References
    
    - `references/mstest.md`
    - `references/patterns.md`
    - `references/anti-patterns.md`
    
    ## Example Requests
    
    - "Fix our MSTest runner setup."
    - "Add an MSTest regression test."
    - "Move this MSTest project to Microsoft.Testing.Platform safely."
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related