June 01, 2013

How to Write Better Unit Tests

When I first wrote unit tests, I thought that coding them was different than coding. Tests weren’t DRY, long and ugly, compared to regular code. But the problem with that is code is code, and ignoring your tests make it harder to understand and maintain. So, this blog post is about changing our thinking on how to write unit tests.

First things first, there are some topics I am assuming you are familiar with in unit testing. These items are crucial to unit testing and if you don’t know them, take some time to read the links below:

 Things to know for Unit Testing: 1)Dependency Injection and SOLID 2)IOC container (optional) 3)Some testing framework (MS Test, XCode, NUnit) 4)Dry 5)Mocking Framework (Moq, Rhino Mocks) 

Now, have you seen unit tests like this?

 [TestClass] public class MyTestClass { [TestMethod] public void MyFirstTestRanSuccessfully() { var expected = true; var logger = new Mock(); var strategy = new Mock(); var data = new parameter() { value1="test" } logger.Setup(p => p.IsDebugEnabled).Returns(true); logger.Setup(p => p.Debug("logged")); var subject = new MyClass(); //The class I am testing subject.Logger = logger.Object; subject.Strategy = strategy.Object; var actual = subject.DoWork(data); Assert.AreNotEqual(expected, actual); strategy.Verify(p => p.DoStrategyWork, Times.Once(), "Did not call DoStrategyWork."); logger.Verify(p => p.IsDebugEnabled, Times.Once(), "Did not call IsDebugEnabled."); logger.Verify(p => p.Debug("logged"), Times.Once(), "Did not call Debug logger."); } } </pre> 

The first issue with MyFirstTest is redundancy. If we were going to create a new test, we would copy the test data, at the beginning of the function, to another function. We don’t want to do that, so let’s create a TestInitialize.

 private Mock logger; private Mock strategy; [TestInitialize()] public void Initalize() { logger = new Mock(); strategy = new Mock(); var data = new parameter() { value1="test" } } [TestMethod] public void MyFirstTestRanSuccessfully() { var expected = true; logger.Setup(p => p.IsDebugEnabled).Returns(true); logger.Setup(p => p.Debug("logged")); var subject = new MyClass(); //The class I am testing subject.Logger = logger.Object; subject.Strategy = strategy.Object; var actual = subject.DoWork(data); Assert.AreNotEqual(expected, actual); strategy.Verify(p => p.DoStrategyWork, Times.Once(), "Did not call DoStrategyWork."); logger.Verify(p => p.IsDebugEnabled, Times.Once(), "Did not call IsDebugEnabled."); logger.Verify(p => p.Debug("logged"), Times.Once(), "Did not call Debug logger."); } </pre> 

Here we are creating sample data we can use in every test. Don’t worry if you change this data in your test, as TestInitialize will reset for each new test. Also, you may see we have moved our mocks as well. This is because they are used over and over in the tests we are going to write for this class. So, put all the things that will be reused in TestInitialize to be more dry.

After putting those tests togther, everything is looking leaner. But there still is a code smell, with unit tests specifically. The test isn’t testing one thing. The test is testing that it ran successfully AND that it logged values. In testing, you want to verify that one thing worked, so that it’s easy to maintain and read these tests. Let’s go ahead and refactor these tests.

 [TestClass] public class MyTestClass { [TestInitialize()] public void Initalize() { logger = new Mock(); strategy = new Mock(); var data = new parameter() { value1="test" } } [TestMethod] public void MyFirstTestLoggedAfterSuccess() { logger.Setup(p => p.IsDebugEnabled).Returns(true); logger.Setup(p => p.Debug("logged")); var subject = new MyClass(); //The class I am testing subject.Logger = logger.Object; subject.Strategy = strategy.Object; var actual = subject.DoWork(data); logger.Verify(p => p.IsDebugEnabled, Times.Once(), "Did not call IsDebugEnabled."); logger.Verify(p => p.Debug("logged"), Times.Once(), "Did not call Debug logger."); } [TestMethod] public void MyFirstTestRanSuccessfully() { var expected = true; var subject = new MyClass(); //The class I am testing subject.Logger = logger.Object; subject.Strategy = strategy.Object; var actual = subject.DoWork(data); Assert.AreNotEqual(expected, actual); strategy.Verify(p => p.DoStrategyWork, Times.Once(), "Did not call DoStrategyWork."); } } </pre> 

Now, those tests look leaner and easier to read. If you notice that a unit test is growing large, it may be an indication that you are doing too much in that class. The rule I have seen is if you have any more dependencies than 3, then that class may be doing too much and needs to be refactored. More of a guideline than a rule. Also, don’t worry if you use different frameworks, as they should be very similar to above. Just remember:

 1)Put all the things that will be reused in TestInitialize to be more dry 2)In testing, you want to verify that one thing worked, so that it's easy to maintain and read these tests. 3)Name the tests to convey the tests being performed. 4)In a test class, keep like tests together in the same class, as they will be sharing the same TestInitialize. 5)If you have more than 3 dependencies, consider refactoring. 

If you have some more tips, feel free to drop them in the comments section.