How to find misplaced or misnamed unit tests with NDepend

In my current project, we do a lot of refactoring to keep the source code as simple and understandable as possible. This involves a lot of renaming of classes and moving classes between namespaces to structure the code in a better understandable way (here is explained how we structure our code). Sometimes, this results in unit tests not renamed or moved along with the production classes they test.

Therefore, I wrote some NDepend queries that show us these misplaced or misnamed test classes.

Our coding guidelines

First, I have to state our coding guidelines regarding unit test structure and naming because the queries below are built against them.

Unit test class name

Each unit test class is named following this pattern:

[prefix]<name of class under test>Test

The prefix is optional and is used when multiple test classes exist for the same class under test. The prefix states which aspect of the class under test is tested in this specific test class. Then follows the name of the class under test and the suffix Test.

For example FooTest is the test class for class Foo and ExceptionCasesBarTest contains only the exception cases tests for class Bar.

Unit test is in the same namespace as the class under test, but in a dedicated test assembly

We put all our unit tests in assemblies named like the production assemblies with suffix .Test. That makes it easy to detect them and simplifies automatic builds.

Furthermore, a unit test class is always in the same namespace as the production class it is testing (we remove the .Test suffix in the default namespace of the test project).

Name of system under test

We always use the name testee for the instance that is under test. This helps to quickly grasp what is tested in a unit test.

[TestFixture]
public class FooTest
{
    private Foo testee;

    [SetUp]
    public void SetUp()
    {
        this.testee = new Foo();
    }

    [Test]
    public void DoesMagic()
    {
        string actual = this.testee.DoMagic();

        actual.Should().Be("magic");  // this is FluentAssertions, check it out!
    }
}

Finding misplaced unit tests

This is the query we use to find misplaced unit test. A misplaced unit test is not in the same namespace as the class it tests:

from p in Assemblies.WithNameWildcardMatchNotIn("*.Test").WithNameWildcardMatch("MyProject*").ChildTypes()
from t in Assemblies.WithNameWildcardMatch("*.Test").ChildTypes()
where t.IsUsing(p)
    && t.Name.EndsWith(p.Name + "Test")
    && t.ParentNamespace.Name != p.ParentNamespace.Name
select new { Class = p, Test = t, ClassNamespace = p.ParentNamespace, TestNamespace = t.ParentNamespace }
  • select all types of our production assemblies (assemblies starting with our project name and not ending in Test)
  • select all types of our unit test assemblies
  • match test class and production class by checking whether the test class uses the production class and matches the name pattern (test class name ends with production class name + Test)
  • take only the found pairs that don’t have the same namespace
  • print the name of the class and test class and their namespaces

Finding misnamed unit tests (containing a testee):

This is the query to find unit test classes with an incorrect name, not following our name pattern:

from p in Assemblies.WithNameWildcardMatchNotIn("*.Test").WithNameWildcardMatch("MyProject*").ChildTypes()
from t in Assemblies.WithNameWildcardMatch("*.Test").ChildTypes()
where t.IsUsing(p)
    && t.Fields.Where(f => f.Name == "testee").Any()
    && t.Fields.Single(f => f.Name == "testee").FieldType == p
    && !t.Name.EndsWith(p.Name.Substring(0, p.Name.IndexOf("<") > 0 ? p.Name.IndexOf("<") : p.Name.Length) + "Test")
    && !t.IsGeneratedByCompiler
select new { Class = p, Test = t, t.ParentNamespace }
  • select all types of our production assemblies (assemblies starting with our project name and not ending in Test)
  • select all types of our unit test assemblies
  • match test class and production class by checking whether the test class uses the production class and the type of the field testee
  • take only class pairs that do not follow the naming convention
  • skip generated classes

Finding unit tests missing TestFixture or not named with suffix Test:

This query finds unit test classes that miss either the TestFixture attribute or the suffix Test in the class name:

from t in Assemblies.WithNameWildcardMatch("*.Test").ChildTypes()
where
(
    t.HasAttribute("NUnit.Framework.TestFixtureAttribute")
    && !t.Name.EndsWith("Test")
) ||
(
    !t.HasAttribute("NUnit.Framework.TestFixtureAttribute")
    && t.Name.EndsWith("Test")
) ||
(
    t.InstanceMethods.Where(m => m.HasAttribute("NUnit.Framework.TestAttribute") || m.HasAttribute("NUnit.Framework.TestCaseAttribute")).Any()
    && !t.Name.EndsWith("Test")
)
orderby t.Name
select new { t, t.ParentNamespace }
  • take all types from a test assembly that either have the TestFixture attribute or end with Test, but miss the other
  • take all types from a test assembly that do have test methods but their name do not end in Test

Conclusions

NDepend queries can easily be used to spot unit test classes not complying with your coding conventions.

This is a great help after a refactoring sessions to check whether all parts are still in their correct place.

These queries can easily be changed to be used for other testing frameworks than NUnit or other coding conventions.

Let me know if you have some cool NDepend queries of your own.

About the author

Urs Enzler

1 comment

By Urs Enzler

Recent Posts