Bulletproof Test Categories For VS Unit Test Framework

I am learning the Visual Studio Unit Testing Framework and I came across the subject of test categories. Test categories allow us to group tests into related buckets so that we can run a subset of our test suite and only include tests from certain categories. We want to place the categories of a test directly in the code so that we can have the benefit of running tests by category without having to maintain a test list.


Image Credit Quinn Dombrowski

The default way to categorize tests in the Visual Studio Unit Testing Framework requires the use of an attribute whose constructor takes a string parameter that specifies the name of the category.

using Microsoft.VisualStudio.TestTools.UnitTesting;

[TestMethod]
[TestCategory("Unit"), TestCategory("Customer")]
public void ShouldHaveNewCustomerAfterAddCommand()
{
}

Over the course of time though, a developer will undoubtedly mistype a category name. Left unwatched, a project will quickly develop a set of miscategorized tests due to fat fingered category names.

[TestCategory("Uint"), TestCategory("Csutomer")]

My first thought on how to eradicate the prevalence of string literals was to use a struct of constant strings to pass into TestCategory's constructor.

public struct CategoryName
{
    public const string Unit = "Unit";
    public const string Customer = "Customer";
}

This allows us to use constant strings that are only declared once. Making it much harder to screw up.

[TestCategory(CategoryName.Unit), TestCategory(CategoryName.Customer)]

With the category names moved to constant strings I felt much better about categorizing my tests, it no longer had that code smell that comes with literal strings littered throughout a project. I still wanted to explore though, something was telling me that an enum would be a better way to define the categories than a struct of const strings.

I wanted to my category declarations to look like the following.

[TestTraits(Trait.Unit, Trait.Customer)]

I changed my terminology from "category" to "trait" because category implies that a test goes in exactly one (which is not necessarily the case) and trait implies that a test can have more than one.

To get the desired syntax, simply inherit from TestCategoryBaseAttribute and make the constructor take an arbitrary number of Traits. Override the TestCategories method and use the Enum.GetName method to retrieve the string representation of each Trait passed into the constructor.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
    
public enum Trait
{
    Unit,
    Customer,
}

public class TestTraitsAttribute : TestCategoryBaseAttribute
{
    private Trait[] traits;

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

    public override IList<string> TestCategories
    {
        get
        {
            var traitStrings = new List<string>();

            foreach (var trait in this.traits)
            {
                string value = Enum.GetName(typeof(Trait), trait);
                traitStrings.Add(value);
            }

            return traitStrings;
        }
    }
}

Now you are free to write your test methods with clear and concise category attributes. Additionally, when you need to add a new category, it's as simple as adding a enum value.

Before:

[TestMethod]
[TestCategory("Unit"), TestCategory("Customer")]
public void ShouldHaveNewCustomerAfterAddCommand()
{
}

After:

[TestMethod]
[TestTraits(Trait.Unit, Trait.Customer)]
public void ShouldHaveNewCustomerAfterAddCommand()
{
}