Configures and runs MSTest (now MSTest.TestFramework v3) - Microsoft's first-party .NET test framework with `[TestClass]` / `[TestMethod]` / `[DataRow]` / `[DynamicData]` attributes; `[ClassInitialize]` / `[ClassCleanup]` / `[TestInitialize]` / `[TestCleanup]` lifecycle; `TestContext` injection; tight Visual Studio + dotnet test integration. Use when working with .NET on a MSTest codebase, or in environments standardized on Microsoft toolchain.
75
94%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Per learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-with-mstest:
For new code, xunit-tests or
nunit-tests are more mainstream.
MSTest is the right pick for Microsoft-mandated environments + legacy.
dotnet new mstest, or the three packages (MSTest.TestFramework, MSTest.TestAdapter, Microsoft.NET.Test.Sdk).[TestClass] and write [TestMethod] tests, asserting with Assert.AreEqual(expected, actual) (expected first).[ClassInitialize] / [TestInitialize] and their cleanup pairs.[DataRow] or [DynamicData] instead of copy-pasted methods.TestContext for per-test metadata + WriteLine output; use [Ignore] (not Assert.Inconclusive) for permanent skips.[TestCategory] and tune .runsettings parallelism (see references/execution-and-ci.md).dotnet test, emitting a .trx logger + coverage in CI.dotnet new mstest -n MyTests
# Or in existing project:
dotnet add package MSTest.TestFramework
dotnet add package MSTest.TestAdapter
dotnet add package Microsoft.NET.Test.SdkPer ms-doc:
using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class CalculatorTests
{
[TestMethod]
public void Adds_TwoNumbers()
{
Assert.AreEqual(3, Calculator.Add(1, 2));
}
}Note: Assert.AreEqual(expected, actual) argument order matches
NUnit, NOT xUnit's (expected, actual) order.
Run: dotnet test.
Per ms-doc:
[TestClass]
public class TestsWithLifecycle
{
[ClassInitialize]
public static void ClassInit(TestContext context) { /* once before all */ }
[ClassCleanup]
public static void ClassCleanup() { /* once after all */ }
[TestInitialize]
public void TestInit() { /* before each test */ }
[TestCleanup]
public void TestCleanup() { /* after each test */ }
[TestMethod]
public void Test1() { ... }
}For assembly-level: [AssemblyInitialize] + [AssemblyCleanup].
[TestMethod]
[DataRow(1, 2, 3)]
[DataRow(0, 0, 0)]
[DataRow(-1, 1, 0)]
public void Adds_VariousInputs(int a, int b, int expected)
{
Assert.AreEqual(expected, Calculator.Add(a, b));
}
// Dynamic data source
[TestMethod]
[DynamicData(nameof(AddCases), DynamicDataSourceType.Method)]
public void Adds_FromDynamic(int a, int b, int expected) { ... }
public static IEnumerable<object[]> AddCases()
{
yield return new object[] { 1, 2, 3 };
yield return new object[] { 0, 0, 0 };
}TestContext is auto-injected per test instance:
[TestClass]
public class TestsWithContext
{
public TestContext TestContext { get; set; } // auto-populated by runner
[TestMethod]
public void LogsContext()
{
TestContext.WriteLine("Test name: {0}", TestContext.TestName);
}
}TestContext provides per-test metadata (test name, deployment dir,
properties from .runsettings) + a WriteLine for output (similar to
xUnit's ITestOutputHelper).
[TestMethod]
[Ignore("Requires staging DB; tracked in JIRA-1234")]
public void Skipped() { }
// Conditional skip via runtime check
[TestMethod]
public void ConditionalTest()
{
if (!IsRunningOnLinux) Assert.Inconclusive("Linux-only test");
// ...
}Assert.Inconclusive marks the test as neither pass nor fail
(distinct from skip).
For [TestCategory] filtering, .runsettings parallel config, and the
CI dotnet test invocation, see
references/execution-and-ci.md.
A team maintaining a Microsoft-toolchain app adds MSTest coverage to
Calculator.Add. They scaffold with dotnet new mstest -n Calc.Tests,
then write a [TestClass] CalculatorTests with three [DataRow] cases
((1, 2, 3), (0, 0, 0), (-1, 1, 0)) on one [TestMethod] asserting
Assert.AreEqual(expected, Calculator.Add(a, b)). A case needing a
staging DB is parked with
[Ignore("Requires staging DB; tracked in JIRA-1234")], and an
end-to-end case is tagged [TestCategory("Integration")] so CI can run
it separately with dotnet test --filter "TestCategory=Integration".
The pipeline runs
dotnet test --logger "trx;LogFileName=test-results.trx" --collect:"XPlat Code Coverage",
producing a .trx report and coverage XML. Result: parametrized MSTest
coverage with the integration case gated out of the fast local loop.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Argument order: Assert.AreEqual(actual, expected) (xUnit-style) | MSTest is (expected, actual); failure messages reversed | Verify order (Step 2) |
Skip [TestClass] annotation | Discovery fails (unlike NUnit which auto-discovers) | Always include [TestClass] |
Use Console.WriteLine instead of TestContext.WriteLine | Output may not appear in test runner | Use TestContext (Step 5) |
Assert.Inconclusive overuse | Tests neither pass nor fail; signals lost | Use [Ignore] for permanent skips (Step 6) |
Assert.AreEqual ordering different from xUnit (migration source
of bugs).[MemberData].xunit-tests,
nunit-tests,
fluentassertions - sister toolstest-code-conventions