Visual Studio [Test Traits]

Karen Payne - Mar 12 '23 - - Dev Community

Introduction

Learn how to categorize unit test methods which is beneficial when there are many test methods and or more than one category.

Note
The information presented only touches the surface, see the Microsoft TechNet article in the see also section for more details.

Jumping in

Imagine having several hundred test methods that might be broken down into several categories, such as a category for data operations, another for API and so forth. In Visual Studio Test Explorer there are options to group test methods.

Test Explorer showing group bt

To use the trait option add the TestCategory attribute to a test method as shown below for MSTest.



[TestClass]
public partial class Dummy : TestBase
{

    [TestMethod]
    [TestCategory("EntityFrameworkCore")]
    public void SomeTest()
    {

    }
}


Enter fullscreen mode Exit fullscreen mode

The same in NUnit



[Test, Category("EntityFrameworkCore")]
public void SomeTest()
{

}


Enter fullscreen mode Exit fullscreen mode

Slight problem

This allow a developer to group by traits now yet there is an issue ready to bite the developer which is, what happens if the category name changes? The developer must go through all test and rename the category.

What can work for any unit test library is to create an enum with categories which if renamed can be refactored to all instances by Visual Studio.



public enum Trait
{
    PlaceHolder,
    EntityFrameworkCore,
    Configurations,
    Controller,
    Services,
    StringHelpers,
    GenericMath,
    TimeOnly
}


Enter fullscreen mode Exit fullscreen mode

For NUnit



[Test, Category(nameof(Trait.StringHelpers))]


Enter fullscreen mode Exit fullscreen mode

For MSTest



[TestMethod, TestCategory(nameof(Trait.StringHelpers))]


Enter fullscreen mode Exit fullscreen mode

Consider creating a class project which stores the enumerations and place the binary in a folder easy to get too or better yet a local NuGet package.

Simple local feed

  1. Create a package

  2. Create a folder e.g. C:\NuGetLocalFeed

  3. Under Visual Studio options for NuGet add the folder

Now when the package is needed, select the local feed. For a more complete method to create a local feed see my Code magazine article Working with NuGet Local Packages.

MSTest specific option

Using the enumeration above, add the following class to the class project or in a unit test project.



/// <summary>
/// Declarative class for using Trait enum about for traits on test method.
/// </summary>
public class TestTraitsAttribute : TestCategoryBaseAttribute
{
    private readonly Trait[] _traits;

    public TestTraitsAttribute(params Trait[] traits)
    {
        _traits = traits;
    }

    public override IList<string> TestCategories => _traits.Select(trait 
        => Enum.GetName(typeof(Trait), trait)).ToList();
}


Enter fullscreen mode Exit fullscreen mode

Now a developer can setup a test method as shown below.



[TestMethod, TestTraits(Trait.StringHelpers)]


Enter fullscreen mode Exit fullscreen mode

If this is appealing, see the project UnitTestHelperLibrary1 in the provided source code.

See also

My Microsoft TechNet article
Visual Studio: Different methods to display unit test

Source code

Clone the following GitHub repository.

There are two unit test projects, one for MSTest and one for NUnit.

Class projects

  • DataLibrary to provide a backend for testing EF Core
  • TeamLibrary, several methods for the test methods
  • UnitTestHelperLibrary contains enum mentioned above
  • UnitTestHelperLibrary1 for MSTest only

Bonus

Check out NaturalStringComparer method in TeamLibrary project for performing a natural sort on strings.



[TestMethod, TestCategory(nameof(Trait.StringHelpers))]
public void NaturalStringSort()
{
    // arrange
    List<string> fileNames = new()
    {
        "Example12.txt", "Example1.txt", "Example2.txt", 
        "Example3.txt", "Example4.txt", "Example10.txt"
    };
    List<string> expected = new()
    {
        "Example1.txt", "Example2.txt", "Example3.txt", 
        "Example4.txt", "Example10.txt", "Example12.txt"
    };

    // act
    fileNames.Sort(new NaturalStringComparer());

    // assert
    CollectionAssert.AreEquivalent(fileNames, expected);

}


Enter fullscreen mode Exit fullscreen mode
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .