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 withTest
, 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.
[…] How to find misplaced or misnamed unit tests with NDepend – an application of CQLinq to detect potential issues with organization, naming, or quality of unit tests. […]