Unit Testing Makes Me Happy
Integrating unit testing into your coding practices can have some positive psychological effects on your programming career.
I'm taking a break from my usual "tutorial-like" columns to talk about how I actually enjoy writing automated unit tests and the positive, psychological effect it has on me during my day-to-day adventures of writing code.
No Unit Tests
I'm sure almost everyone reading this has come across an existing code base that had little to no unit testing, sometimes even with an extremely large code base that runs mission-critical business functionality. And developers are in this code base daily -- making changes, rearranging code -- relying solely on manual unit testing of the most basic execution paths.
It's not a fun position in which to be. This type of environment leads to more stress for developers. A simple, one-line fix for a reported bug can simultaneously introduce two, three or more new bugs. For a new developer, it can be demoralizing -- he's trying to make a good impression, yet his efforts can produce unintended side effects. His drive to jump in and learn the code can be reduced greatly as he fixes bugs .
The affect on a seasoned developer can be similar. She might have spent years in the code base: making changes, adding features and generally improving the business as a result of her efforts. She might even be considered the "go to" person for certain parts of the code. A simple tweak, even as basic as changing some default value, could have numerous unknown consequences. Suddenly, the go-to person is now taking longer to get bugs fixed because she's second-guessing herself or spending an inordinate amount of time tracking down every execution path to make sure she isn't introducing new bugs.
This type of impact on a developer shouldn't be overlooked. A business can throw all sorts of utilities and amenities at the developer (lots of RAM, multiple monitors, Resharper Licenses and so on …) but if he fears the process of making a code change, everyone in the business will feel the effects.
Writing Unit Tests
Developers often balk at writing unit tests for existing code. The code has been running a long time, perhaps years, without issues. No unit tests have ever been needed, so why start now? Why "waste" time developing some unit tests that will probably always pass?
The biggest reason I can think of? Confidence. When I come across some code that needs to be changed, I immediately look for the unit tests. Unit tests can reveal much about the code, sometimes more than documentation. (And, let's be honest: Code documentation for in-house projects can be as difficult to find as unit tests!) Unit tests let me change the code and have the confidence in knowing my changes won't (and shouldn't) introduce any new bugs.
Writing unit tests before modifying code should be a requirement. In many places it is, but there are also many places where it isn't. Some code, especially older code, doesn't lend itself to being "unit testable" -- maybe it has strong coupling, no dependency injection, no factories and so on. All of these are hindrances to writing unit tests, for sure. But I try my hardest to write at least a basic set of unit tests around the functionality I'm about to change.
I'm not going to go in to all of the details of writing unit tests (testing styles, frameworks, utilities and so on). -- there's plenty of articles that cover that. This article is about the psychological impact those tests will have on me, the developer:
- I'll feel more confident that I'm not introducing a new bug.
- I'll feel more confident that I've fixed the bug I was assigned to fix.
- I'll feel more confident that future developers will have these unit tests to review when they first start modifying the code.
For a moment, forget about the dollars and cents ("I'm not paying you to test old code," "Fixing bugs and writing unit tests means less bug fixing"). While there is a bottom line on a spreadsheet somewhere, there is also the developer who wants to write good code. She wants to fix bugs and move the code base forward. She wants to be able to fix a lot of bugs quickly. A suite of unit tests will help accomplish that! And that makes me happy.
Refactoring and Unit Tests
A common issue with older code is how it's organized. As a developer, you know the pain of modifying unstructured code. It's tedious and time-consuming. A developer likes to refactor code to improve its maintainability, its stability and more. But how can you really take apart code and refactor it if you don't have unit tests to verify that it still works after refactoring?
When I need (not want, but need) to refactor some older code, I make an effort to start by writing a handful of unit tests. I may not be able to hit 100 percent (or even 85 percent or 70 percent) test coverage, but I make an effort to concentrate on the area of the code I'm refactoring. Besides the confidence I get by knowing there's a good chance I won't introduce a new bug, this sort of refactoring helps me gain a better understanding of the code. As I'm writing the unit tests and thinking about things to test for, I learn more about "what if" scenarios that the original code may not even take into account.
And what, exactly, do I do when I come across an obvious error situation that I could correct? Most of the time, I don't correct it. Consider this simple example:
public static string Last4Digits(string value)
return value.Substring(value.Length - 4);
It has two obvious issues:
- No null checks. If a null is passed in for the value parameter, this method will throw a NullReferenceException.
- If a string of less than four characters is passed in for the value parameter, this method will throw an ArgumentOutOfRangeException.
The "correct" programmer in me wants to add some null checks and a length check. However, that could break existing code. Maybe a caller looks for an ArgumentOutOfRangeException to know that a string of less than four digits was passed in. Unless I know exactly every instance and place this method could be called and every possible value that may be passed, I can't reliably modify this method by adding new validations to it.
But, when I need to modify this function and I'm going to start with some unit tests, there are two unit tests I'll add right away:
- If a null value is passed in, a unit test will make sure a NullReferenceException is thrown.
- If a value of less than four digits is passed in, a unit test will make sure an ArgumentOutOfRangeException occurs.
This may seem like a waste of time, but it gives me a set of unit tests that accurately tests the expectations of the method as it's currently written and used.
Three months later, if another developer comes across this method and thinks "Oh! No null checks? I'll clean this up!", my unit tests will catch his "fix" (which has the potential of breaking more code). The break in the unit test will be caught during development and not weeks later in production when something breaks because some other piece of code was looking for a NullReferenceException.
Situations like this will boost the confidence of a novice developer. His mistake is found during local development -- and before deployment. Hopefully, he'll inquire as to lack of a null check and if I'm around, I'll gladly explain the reasons behind it. Now there's one more developer on the team who knows a little bit more about the code and why it's written the way it is. That makes me happy.
When talking with other developers about the value of writing unit tests for existing code, I almost always hear, "I don't have time to write unit tests. I've got bugs to fix and my boss won't give me time to write unit tests." If that's the case, write them on your own time. Seriously. Take an hour or two a week and write some unit tests for existing code. Try to change the code as little as possible to support unit testing. Remember, you don't want to introduce new bugs. You'll learn more about the code, you'll feel better about having unit tests and you'll become more valuable to your employer -- as well as to other employers!
Do Unit Tests, Be Happy
Writing unit tests isn't something I avoid -- it makes me happy and I feel more confident in the code I'm modifying. I know there's less risk in making changes and I know it makes an impact on future developers because they have something that can describe what the code is expected to do.
And don't dismiss the value of writing unit tests, especially for older code. While there are many tangible benefits, such as 100 percent code coverage, lower occurrence of regression bugs and so on, there's the intangible benefit of feeling good about the code you're producing.
Patrick Steele is a senior .NET developer with Billhighway in Troy, Mich. A recognized expert on the Microsoft .NET Framework, he’s a former Microsoft MVP award winner and a presenter at conferences and user group meetings.