Skip to content

Add TestCategories property to ITestDataRow for per-test-case categorization #5795

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
112 changes: 112 additions & 0 deletions EXAMPLE_USAGE.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

// EXAMPLE: Using TestCategories with TestDataRow
// This demonstrates the new TestCategories functionality added to ITestDataRow

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace ExampleUsage;

[TestClass]
public class TestCategoriesExampleTests
{
/// <summary>
/// Example showing how to use TestCategories with TestDataRow for dynamic data.
/// Each test case can now have its own categories.
/// </summary>
[TestMethod]
[DynamicData(nameof(GetTestDataWithCategories), DynamicDataSourceType.Method)]
public void ExampleTestWithDynamicCategories(string value, string expectedResult)
{
// Test logic here
Assert.AreEqual(expectedResult, ProcessValue(value));
}

/// <summary>
/// Example showing how TestCategories from TestDataRow merge with method-level categories.
/// This test method has "MethodLevel" category, and individual test cases add their own.
/// </summary>
[TestCategory("MethodLevel")]
[TestMethod]
[DynamicData(nameof(GetTestDataWithMergedCategories), DynamicDataSourceType.Method)]
public void ExampleTestWithMergedCategories(string value)
{
// Test logic here
Assert.IsNotNull(ProcessValue(value));
}

/// <summary>
/// Test data source that demonstrates TestCategories usage.
/// </summary>
public static IEnumerable<object[]> GetTestDataWithCategories()
{
// Fast unit test case
yield return new TestDataRow<(string, string)>(("input1", "output1"))
{
TestCategories = new List<string> { "Unit", "Fast" },
DisplayName = "Fast unit test case"
};

// Slow integration test case
yield return new TestDataRow<(string, string)>(("input2", "output2"))
{
TestCategories = new List<string> { "Integration", "Slow", "Database" },
DisplayName = "Integration test with database"
};

// Performance test case
yield return new TestDataRow<(string, string)>(("input3", "output3"))
{
TestCategories = new List<string> { "Performance", "Load" },
DisplayName = "Load testing scenario"
};

// Regular test case without specific categories
yield return new TestDataRow<(string, string)>(("input4", "output4"))
{
DisplayName = "Standard test case"
// No TestCategories specified - will inherit any from method/class/assembly level
};

// Traditional data row still works
yield return new object[] { "input5", "output5" };
}

/// <summary>
/// Test data that will merge with method-level categories.
/// </summary>
public static IEnumerable<object[]> GetTestDataWithMergedCategories()
{
// This will have both "MethodLevel" (from method attribute) and "DataLevel" categories
yield return new TestDataRow<string>("test_value")
{
TestCategories = new List<string> { "DataLevel", "Specific" },
DisplayName = "Test with combined categories"
};
}

private static string ProcessValue(string input) => $"processed_{input}";
}

/*
* Usage scenarios enabled by this feature:
*
* 1. FILTERING BY CATEGORY:
* - Can now run tests filtered by categories applied to individual test cases
* - Example: dotnet test --filter "TestCategory=Fast" will run only fast test cases
* - Example: dotnet test --filter "TestCategory=Integration" will run only integration test cases
*
* 2. MIXED SCENARIOS:
* - Can have different types of test cases (unit, integration, performance) in same test method
* - Each test case can have appropriate categories for filtering/organization
*
* 3. CATEGORY INHERITANCE:
* - Test cases inherit categories from method/class/assembly level
* - TestDataRow categories are merged with existing categories (no overriding)
*
* 4. BACKWARD COMPATIBILITY:
* - Existing TestDataRow usage continues to work unchanged
* - TestCategories property is optional (nullable)
* - Regular object[] data rows continue to work
*/
Original file line number Diff line number Diff line change
Expand Up @@ -439,14 +439,26 @@
foreach (object?[] dataOrTestDataRow in data)
{
object?[] d = dataOrTestDataRow;
if (TestDataSourceHelpers.TryHandleITestDataRow(d, methodInfo.GetParameters(), out d, out string? ignoreMessageFromTestDataRow, out string? displayNameFromTestDataRow))
if (TestDataSourceHelpers.TryHandleITestDataRow(d, methodInfo.GetParameters(), out d, out string? ignoreMessageFromTestDataRow, out string? displayNameFromTestDataRow, out IList<string>? testCategoriesFromTestDataRow))
{
testDataSourceIgnoreMessage = ignoreMessageFromTestDataRow ?? testDataSourceIgnoreMessage;
}

UnitTestElement discoveredTest = test.Clone();
discoveredTest.DisplayName = displayNameFromTestDataRow ?? dataSource.GetDisplayName(methodInfo, d) ?? discoveredTest.DisplayName;

// Merge test categories from the test data row with the existing categories
if (testCategoriesFromTestDataRow is { Count: > 0 })
{
string[] existingCategories = discoveredTest.TestCategory ?? [];
var mergedCategories = new HashSet<string>(existingCategories);
foreach (string category in testCategoriesFromTestDataRow)
{
mergedCategories.Add(category);
}

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build MacOS Release)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx (Build Linux Debug)

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,17): error IDE2003: (NETCORE_ENGINEERING_TELEMETRY=Build) Blank line required between block and subsequent statement (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/style-rules/ide2003)

Check failure on line 458 in src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs

View check run for this annotation

Azure Pipelines / microsoft.testfx

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs#L458

src/Adapter/MSTestAdapter.PlatformServices/Discovery/AssemblyEnumerator.cs(458,18): error SA1513: (NETCORE_ENGINEERING_TELEMETRY=Build) Closing brace should be followed by blank line (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1513.md)
discoveredTest.TestCategory = [.. mergedCategories];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot please add a blank line between the closing } and this line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added proper blank line spacing between the closing brace and the following comment (41270af)

}

// If strategy is DisplayName and we have a duplicate test name don't expand the test, bail out.
#pragma warning disable CS0618 // Type or member is obsolete
if (test.TestMethod.TestIdGenerationStrategy == TestIdGenerationStrategy.DisplayName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,22 @@ public static bool TryHandleITestDataRow(
out object?[] data,
out string? ignoreMessageFromTestDataRow,
out string? displayNameFromTestDataRow)
=> TryHandleITestDataRow(d, testMethodParameters, out data, out ignoreMessageFromTestDataRow, out displayNameFromTestDataRow, out _);

public static bool TryHandleITestDataRow(
object?[] d,
ParameterInfo[] testMethodParameters,
out object?[] data,
out string? ignoreMessageFromTestDataRow,
out string? displayNameFromTestDataRow,
out IList<string>? testCategoriesFromTestDataRow)
{
if (d.Length == 1 && d[0] is ITestDataRow testDataRow)
{
object? dataFromTestDataRow = testDataRow.Value;
ignoreMessageFromTestDataRow = testDataRow.IgnoreMessage;
displayNameFromTestDataRow = testDataRow.DisplayName;
testCategoriesFromTestDataRow = testDataRow.TestCategories;

data = TryHandleTupleDataSource(dataFromTestDataRow, testMethodParameters, out object?[] tupleExpandedToArray)
? tupleExpandedToArray
Expand All @@ -36,6 +46,7 @@ public static bool TryHandleITestDataRow(
data = d;
ignoreMessageFromTestDataRow = null;
displayNameFromTestDataRow = null;
testCategoriesFromTestDataRow = null;
return false;
}

Expand Down
2 changes: 2 additions & 0 deletions src/TestFramework/TestFramework/ITestDataRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,6 @@ internal interface ITestDataRow
string? IgnoreMessage { get; }

string? DisplayName { get; }

IList<string>? TestCategories { get; }
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#nullable enable
Microsoft.VisualStudio.TestTools.UnitTesting.Assert.AssertNonStrictThrowsInterpolatedStringHandler<TException>.AssertNonStrictThrowsInterpolatedStringHandler(int literalLength, int formattedCount, System.Func<object?>! action, out bool shouldAppend) -> void
Microsoft.VisualStudio.TestTools.UnitTesting.ConditionBaseAttribute.Mode.get -> Microsoft.VisualStudio.TestTools.UnitTesting.ConditionMode
Microsoft.VisualStudio.TestTools.UnitTesting.TestDataRow<T>.TestCategories.get -> System.Collections.Generic.IList<string!>?
Microsoft.VisualStudio.TestTools.UnitTesting.TestDataRow<T>.TestCategories.set -> void
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.IsGreaterThan<T>(T lowerBound, T value) -> void
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.IsGreaterThan<T>(T lowerBound, T value, string? message) -> void
static Microsoft.VisualStudio.TestTools.UnitTesting.Assert.IsGreaterThan<T>(T lowerBound, T value, string? message, params object?[]? parameters) -> void
Expand Down
6 changes: 6 additions & 0 deletions src/TestFramework/TestFramework/TestDataRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public TestDataRow(T value)
[DataMember]
public string? DisplayName { get; set; }

/// <summary>
/// Gets or sets the test categories for the test case.
/// </summary>
[DataMember]
public IList<string>? TestCategories { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Youssef1313 what about using a readonly collection here? We don't really expect to modify the collection from our end. The benefit of keeping it this way is to allow users to create the TestDataRow instance and add categories after but the logic could be reverted.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept IList<string>? to maintain the common initialization pattern where users create a List<string> and assign it to TestCategories. Changing to IReadOnlyList<string>? would require interface changes and break the typical usage pattern shown in tests and examples. The interface already provides read-only access (IList<string>? TestCategories { get; }), so consumers through the interface can't modify the collection.


/// <inheritdoc cref="Value"/>
object? ITestDataRow.Value => Value;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.MSTestV2.CLIAutomation;

namespace MSTest.IntegrationTests;

public class TestCategoriesFromTestDataRowTests : CLITestBase
{
private const string TestAssetName = "TestCategoriesFromTestDataRowProject";

[TestMethod]
public async Task TestCategoriesFromTestDataRow_ShouldDiscoverTestsWithIntegrationCategory()
{
// Arrange
string assemblyPath = GetAssetFullPath(TestAssetName);

// Act - Filter by "Integration" category which should come from TestDataRow
System.Collections.Immutable.ImmutableArray<Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase> testCases = DiscoverTests(assemblyPath, "TestCategory~Integration");

// Assert - Should discover the test that has Integration category from TestDataRow
Assert.AreEqual(1, testCases.Length, "Should discover exactly 1 test with Integration category");

var integrationTest = testCases[0];
Assert.IsTrue(integrationTest.DisplayName.Contains("Integration and Slow"), "Should be the test with Integration and Slow categories");
}

[TestMethod]
public async Task TestCategoriesFromTestDataRow_ShouldDiscoverTestsWithUnitCategory()
{
// Arrange
string assemblyPath = GetAssetFullPath(TestAssetName);

// Act - Filter by "Unit" category which should come from TestDataRow
System.Collections.Immutable.ImmutableArray<Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase> testCases = DiscoverTests(assemblyPath, "TestCategory~Unit");

// Assert - Should discover the test that has Unit category from TestDataRow
Assert.AreEqual(1, testCases.Length, "Should discover exactly 1 test with Unit category");

var unitTest = testCases[0];
Assert.IsTrue(unitTest.DisplayName.Contains("Unit and Fast"), "Should be the test with Unit and Fast categories");
}

[TestMethod]
public async Task TestCategoriesFromTestDataRow_ShouldCombineMethodAndDataCategories()
{
// Arrange
string assemblyPath = GetAssetFullPath(TestAssetName);

// Act - Filter by "MethodLevel" category (method attribute)
System.Collections.Immutable.ImmutableArray<Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase> methodLevelTests = DiscoverTests(assemblyPath, "TestCategory~MethodLevel");

// Act - Filter by "DataLevel" category (from TestDataRow)
System.Collections.Immutable.ImmutableArray<Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase> dataLevelTests = DiscoverTests(assemblyPath, "TestCategory~DataLevel");

// Assert - The same test should be found by both filters since categories are merged
Assert.AreEqual(1, methodLevelTests.Length, "Should discover exactly 1 test with MethodLevel category");
Assert.AreEqual(1, dataLevelTests.Length, "Should discover exactly 1 test with DataLevel category");

var methodTest = methodLevelTests[0];
var dataTest = dataLevelTests[0];

Assert.AreEqual(methodTest.FullyQualifiedName, dataTest.FullyQualifiedName, "Both filters should find the same test");
Assert.IsTrue(methodTest.DisplayName.Contains("method and data categories"), "Should be the test with combined categories");
}

[TestMethod]
public async Task TestCategoriesFromTestDataRow_ShouldExecuteCorrectly()
{
// Arrange
string assemblyPath = GetAssetFullPath(TestAssetName);

// Act
System.Collections.Immutable.ImmutableArray<Microsoft.VisualStudio.TestPlatform.ObjectModel.TestCase> testCases = DiscoverTests(assemblyPath, "FullyQualifiedName~TestCategoriesFromTestDataRowTests");
System.Collections.Immutable.ImmutableArray<Microsoft.VisualStudio.TestPlatform.ObjectModel.TestResult> testResults = await RunTestsAsync(testCases);

// Assert - All tests should pass
VerifyE2E.TestsPassed(
testResults,
"TestMethodWithDynamicDataCategories (Test with Integration and Slow categories)",
"TestMethodWithDynamicDataCategories (Test with Unit and Fast categories)",
"TestMethodWithDynamicDataCategories (Test with no additional categories)",
"TestMethodWithDynamicDataCategories (value4,4)",
"TestMethodWithMethodLevelCategoriesAndDataCategories (Test with method and data categories)");

VerifyE2E.FailedTestCount(testResults, 0);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net462</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="MSTest.TestFramework" Version="*" />
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestCategoriesFromTestDataRowProject;

[TestClass]
public class TestCategoriesFromTestDataRowTests
{
[TestMethod]
[DynamicData(nameof(GetTestDataWithCategories), DynamicDataSourceType.Method)]
public void TestMethodWithDynamicDataCategories(string value, int number)
{
Assert.IsTrue(!string.IsNullOrEmpty(value));
Assert.IsTrue(number > 0);
}

public static IEnumerable<object[]> GetTestDataWithCategories()
{
// Test data row with categories
yield return new TestDataRow<(string, int)>(("value1", 1))
{
TestCategories = new List<string> { "Integration", "Slow" },
DisplayName = "Test with Integration and Slow categories"
};

// Test data row with different categories
yield return new TestDataRow<(string, int)>(("value2", 2))
{
TestCategories = new List<string> { "Unit", "Fast" },
DisplayName = "Test with Unit and Fast categories"
};

// Test data row with no categories (should inherit from method level)
yield return new TestDataRow<(string, int)>(("value3", 3))
{
DisplayName = "Test with no additional categories"
};

// Regular data row (not TestDataRow) - should work as before
yield return new object[] { "value4", 4 };
}

[TestCategory("MethodLevel")]
[TestMethod]
[DynamicData(nameof(GetTestDataWithCategoriesForMethodWithCategory), DynamicDataSourceType.Method)]
public void TestMethodWithMethodLevelCategoriesAndDataCategories(string value)
{
Assert.IsTrue(!string.IsNullOrEmpty(value));
}

public static IEnumerable<object[]> GetTestDataWithCategoriesForMethodWithCategory()
{
// This should have both "MethodLevel" and "DataLevel" categories
yield return new TestDataRow<string>("test")
{
TestCategories = new List<string> { "DataLevel" },
DisplayName = "Test with method and data categories"
};
}
}
Loading
Loading