Writing good code that can be trusted to work means unit testing your code. In order to effectively maintain these tests you will need to follow the same principles you would with any other code. This of course means that you extract logic to achieve code reuse, name methods and objects clearly, use composition, and use inheritance.
In this post I am going to show how you can get some code reuse when you’re following another good testing practice. You should keep things well abstracted. I like to keep my tests in folders and I keep test classes in those folders. I do this so that I can have each test class be small and only be testing one things. By doing this I make my test classes a lot cleaner and easier to work with. This means, however, that if I do not extract some of the duplication I could be creating more maintenance work later.
On way that I keep my tests more maintainable is to keep duplicated logic for initialization in base classes. When I am testing methods of a certain class, I will keep a folder of those tests and have a class for each area of the code I want to test. This lets me keep some of the initialization logic in a base class. Many of the test classes will need an instance of the object being tested, so I can put logic in the base class to initialize my mock objects pass them to the constructor of the class I will be testing.
Setting up a base class is easy. The following examples show how to set up a base test class in MSTest and NUnit.
Setting Up a Base Test Class with MSTest
[TestClass]
public class BaseTestClass
{
public BaseTestClass()
{
Console.WriteLine("BaseTestClass.Ctor()");
}
[TestInitialize]
public void BaseTestInitialize()
{
Console.WriteLine("BaseTestClass.BaseTestInitialize()");
}
[TestCleanup]
public void BaseTestCleanup()
{
Console.WriteLine("BaseTestClass.BaseTestCleanup()");
}
}
[TestClass]
public class ConcreteTestClass :BaseTestClass
{
public ConcreteTestClass()
{
Console.WriteLine("ConcreteTestClass.Ctor()");
}
[TestInitialize]
public void MyTestInitialize()
{
Console.WriteLine("ConcreteTestClass.MyTestInitialize()");
}
[TestCleanup]
public void MyTestCleanup()
{
Console.WriteLine("ConcreteTestClass.MyTestCleanup()");
}
[TestMethod]
public void TestMethod1()
{
Console.WriteLine("ConcreteTestClass.TestMethod1()");
}
[TestMethod]
public void TestMethod2()
{
Console.WriteLine("ConcreteTestClass.TestMethod2()");
}
}
Setting Up a Base Test Class using NUnit
[TestFixture]
public class BaseTestClass
{
public BaseTestClass()
{
Console.WriteLine("BaseTestClass.Ctor()");
}
[SetUp]
public void BaseSetUp()
{
Console.WriteLine("BaseTestClass.SetUp()");
}
[TearDown]
public void BaseTearDown()
{
Console.WriteLine("BaseTestClass.TearDown()");
}
}
[TestFixture]
public class ConcreteTestClass : BaseTestClass
{
public ConcreteTestClass()
{
Console.WriteLine("ConcreteTestClass.Ctor()");
}
[SetUp]
public void SetUp()
{
Console.WriteLine("ConcreteTestClass.SetUp()");
}
[TearDown]
public void TearDown()
{
Console.WriteLine("ConcreteTestClass.TearDown()");
}
[Test]
public void TestMethod1()
{
Console.WriteLine("ConcreteTestClass.TestMethod1()");
}
[Test]
public void TestMethod2()
{
Console.WriteLine("ConcreteTestClass.TestMethod2()");
}
}
Notice in these examples how the Base method setups happen first and then the local class ones. This allows you to depend on the code happening on the base class first. Then when you tear everything down you will first clear up things in the local method and then the base class will run.
Make sure you follow this example by having different names for the base methods so they don’t collide with the local names. This lets you have them both run without one having to call the other. If the names match you will either be overriding the base or hiding it, but they will not both run.
Comments