.NET unit testing (C# / F# / VB.NET) with xUnit.net as the primary framework - `[Fact]` single tests, `[Theory]` + `[InlineData]`/`[ClassData]`/`[MemberData]` parametrization, class and collection fixtures (`IClassFixture` / `ICollectionFixture`), parallel-execution config, `ITestOutputHelper` output, skip/traits filtering, and `dotnet test` CI with trx + coverage. Includes framework choice (xUnit for new projects; match an existing NUnit/MSTest convention detected from csproj PackageReferences; legacy .NET Framework 4.x → NUnit or MSTest) and test-authoring conventions (AAA mapping, argument-order traps, no fabricated methods, no smoke asserts). References cover NUnit (`[TestCase]`, constraint-model `Assert.That`), MSTest (`[TestClass]` / `[DataRow]` / TestContext), and the FluentAssertions `.Should()` catalog including the v8 commercial-license change. Use for any .NET unit-test task: choosing or configuring a framework, writing or parameterizing tests, fixtures, or wiring CI.
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
Companion reference for dotnet-unit-tests. Consult for existing MSTest
projects (the Visual Studio default before ~2018) or Microsoft-toolchain
shops standardized on first-party tooling. For new code, xUnit (SKILL.md)
or NUnit (nunit.md) are more mainstream.
Per learn.microsoft.com/dotnet/core/testing/unit-testing-with-mstest:
dotnet new mstest -n MyTests
# Or: dotnet add package MSTest.TestFramework + MSTest.TestAdapter + Microsoft.NET.Test.Sdkusing Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class CalculatorTests
{
[TestMethod]
public void Adds_TwoNumbers()
{
Assert.AreEqual(3, Calculator.Add(1, 2));
}
}[TestClass] is required - unlike NUnit, discovery fails without it.
Assert.AreEqual(expected, actual) takes expected first. Run:
dotnet test.
Per ms-doc: [ClassInitialize] / [ClassCleanup] (static, once
per class - ClassInitialize receives a TestContext), [TestInitialize] /
[TestCleanup] (per test), and [AssemblyInitialize] /
[AssemblyCleanup] at assembly level.
[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 };
}Auto-injected per test instance - per-test metadata (test name,
deployment dir, .runsettings properties) plus WriteLine output
(the MSTest analog of xUnit's ITestOutputHelper):
[TestClass]
public class TestsWithContext
{
public TestContext TestContext { get; set; } // auto-populated by runner
[TestMethod]
public void LogsContext()
{
TestContext.WriteLine("Test name: {0}", TestContext.TestName);
}
}[Ignore("Requires staging DB; tracked in JIRA-1234")] for permanent
skips; Assert.Inconclusive("...") for runtime conditional skips (marks
neither pass nor fail - don't overuse it, signals get lost).
[TestMethod]
[TestCategory("Integration")]
public void IntegrationTest() { }
// Filter: dotnet test --filter "TestCategory=Integration".runsettings parallelism:
<RunSettings>
<RunConfiguration>
<MaxCpuCount>4</MaxCpuCount>
</RunConfiguration>
<MSTest>
<Parallelize>
<Workers>4</Workers>
<Scope>MethodLevel</Scope>
</Parallelize>
</MSTest>
</RunSettings>Scope: MethodLevel (parallel within class) or ClassLevel (parallel
across classes only).
- run: dotnet test --logger "trx;LogFileName=test-results.trx" \
--collect:"XPlat Code Coverage" \
--settings test.runsettings| Anti-pattern | Why it fails | Fix |
|---|---|---|
Assert.AreEqual(actual, expected) reversed | MSTest is (expected, actual); misleading diffs | Expected first, or FluentAssertions (fluentassertions.md) |
Missing [TestClass] | Discovery fails | Always include |
Console.WriteLine for output | May not appear in the runner | TestContext.WriteLine |
Assert.Inconclusive overuse | Tests neither pass nor fail | [Ignore] for permanent skips |
[DynamicData] is less ergonomic than xUnit's [MemberData].