
Unit testing should be a way of life by now for all developers. It should be something that every developer does with every project. We should be running code coverage analysis on all our projects to help ensure that we are building the right tests. Should be.
Mainstream adoption of unit testing seems to be making progress - more and more development groups are testing at least some error prone portions of their code or are adding unit tests when new code is added to a project. These are healthy choices for any development group.
For those development groups that pick up unit testing late in the game, they face a tough challenge. Realistically, we have to face the fact that unit test coverage will start very small and grow over time. Because it takes so long to write good unit tests, and because code that was written without unit testing forethought is tough to test, we have to accept a slow transition. Below, I share my ideas about how to write good unit tests. This article will cover NUnit specifically, but many of the ideas are equally applicable to any unit testing framework.
I have a few theories about unit testing that have shaped my unit testing practices.
Testing Is Almost Always More Important Than Encapsulation
Good unit testing is almost more important than OO encapsulation. That is, when design decisions regarding the interface of my classes, I choose will choose to make the classes more testable than more encapsulated. On public interfaces, I am lean a little more in the direction of encapsulation so the end user isn't either 1) pained by lots of testing baggage on the class's interface or 2) exposed to methods that have the potential to cause serious run-time problems. If you are working in an assembly that will never be exposed to the end user, take advantage of your envious position and lean heavily toward making your classes easier to test more effectively by compromising your 'public' interface.
Consider Testing Needs Early and Often
Unit testing should be integrated into every facet of your development. When you design classes, when you decide on parameters to methods, when you design the tasks the methods will perform, when you design methods that will require complex objects to be provided as parameters, consider the testing requirements of the system. It will often change your perspective in ways that you will be pleased about later.
Find Good Tools
Unit testing is very easy to skip or postpone - if it is hard to run the tests, you will be much less likely to run them early and often. A good tool will make it easier to execute the tests, view the test results, debug the tests (step through the code), and calculate/display code coverage results. You will likely purchase these tools, but a good tool is worth the investment. Unit testing tools that make testing easier will help you to get in and stay in the practice of testing early and often.
Writing Unit Tests - Best Practices
Locate your tests in a separate assembly
Put unit tests in a new project separate from the project you are testing. Name the unit testing project using the same naming convention used to name the assembly being tested, adding ".NUnit" at the end of the project name. If the assembly is named CorporateLibrary, your unit test project should be named phc_CorporateLibrary.NUnit. The uniformity in naming convention will make it easier to find the unit test library. In addition, if your group uses more than one type of unit testing, you will be able to distinguish your NUnit test classes from other types.
Inside your testing project, name the classes that contain the unit tests using the same name as the class being tested, adding "_Test" to the end of the class name. Since you will probably be testing multiple classes, separate the tests for each class in its equivalent class in the test project. If you have used folders to organize your project, use the same folder names and hierarchy to organize your test classes.
One Test Per Method
Each method should test a single piece of functionality of the method class or method it is testing. You should generally opt for lots of test methods over fewer test methods that test lots of functionality. The results of your testing will give a finer granularity of error results that will help determine the nature of the problem that caused the test to fail.
Instead of creating a single test method to test the return value of a function given different parameters, create multiple tests, one for each set of input parameters. This will help you to quickly identify which test failed when you see a red error result in the NUnit GUI.
Put Your Test Class(es) in a Separate Assembly
Name Your Tests for Easy Identification
Name your tests using the following convention: Test_<methodbeingtested>_<brieftestdescription>. If you are writing a unit test to ensure that the GetString method handles Nothing parameters correctly, you might name your test "Test_GetString_Nothing".
Do Not Write Unnecessary Output
Do not write unnecessary output inside your unit test unless it is required to verify that the unit test was successful. In most cases, your test should be written such that a failure is reported using one of the standard error reporting methods, normally a series of Assert* methods.
Never (Almost) Catch Exceptions in Unit Tests
Unless there is a compelling reason to handle an exception in your unit test, it is better to avoid any error handling in your unit test methods. Instead, allow NUnit to capture the exception and report it in the unit test results. If the test method expects an exception, meaning that an exception being raised means the test passes, then use NUnit's ExpectedException attribute. If you decorate a test method with the ExpectedException attribute, and the method does not throw an exception, the unit test fails.
NUnit's ExpectedException attribute informs NUnit that it should expect an exception when it executes the test method. You can specify the exception type (using the .NET Framework's GetType() function), the exception name (same as the type, but you specify the name instead, making the code easier to write and easier to look at), the exception message (the ex.Message string returned on the exception), and more. You can use any or all of the parameters you need to be sure the correct exception was thrown. You may also specify a UserMessage that NUnit should report to help the person running the test understand why the test failed.
<Test()> _
<ExpectedException(ExceptionName:="System.ArgumentException", _
ExpectedMessage:="The LogText parameter was either empty or Null.")> _
Public Sub Test_ApplicationLog_WriteLog_VerifyParm_Nothing()
Dim strValue As String
strValue = ApplicationLog_WriteLog(Nothing)
End Sub
In the example above, the name and message of the expected exception are specified, along with an informational message to the tester about the exception. Also notice that since an exception is expected when the ConfigurationCache_Read_String method is called, there is no need to use one of the normal assert methods.
Write Code That Is Easy to Test
This is a personal impression, but I think when you find it difficult to write good unit tests for classes and methods, this is a reflection of the quality of the code itself. It may not be broken down into methods to perform a single discrete task, or there may be a tremendous amount of 'state' that is required to be present to test a method (such as constructing a complex set of objects).
In other cases, your classes are very tightly coupled to other classes, making it difficult to for one to operate properly without the other. Find ways to make the connection between these classes more loosely coupled. This will allow you to replace objects that are difficult to fabricate with mock versions that do nothing more than satisfy the needs of the unit test.
In some cases, there's not much you can do about these problems. However, take a moment to look at your code and decide how large the methods are, and if breaking them down into smaller bits of functionality might help to make writing unit tests easier.
Don't Ignore Private and Friend Methods
The bulk of the code in a well-written class is probably hidden inside Private and Friend scoped methods. Don't ignore these methods, test them.
The recommendation to test Private and Friend methods begs the question: how do you test those methods when the test code is in a separate assembly? Recall, one of my theories is that good testing is more important than perfectly ripe bowls of encapsulation. I am willing to sacrifice encapsulation in the name of better and more efficient testing. The answer is to change some of your Private methods to Friend methods, and apply a .NET Framework technique that allows you to expand the Friend scope to include another assembly. See my post from Feb 13, 2008 on how to do this. I understand how naughty this sounds, how unorthodox this sounds, but it is a reasonable trade-off to get solid unit tests for your projects.


