When you start writing unit tests, the tests that seem useful might be too involved to give a quick payback. The ones you write quickly might seem too trivial to add value. By starting with small, seemingly simple tests, you can both overcome the inertia that stops you from testing and add value to your project.
There are two claims that people who want to adopt unit testing often make when they find themselves writing few tests: Tests that will be useful are too hard and too time-consuming to write, and the code that they want to test seems too trivial to bother with when the problems can be found through inspection. People's initial view of the risks and value of testing strategies isn't always accurate. Unit Tests—by which we mean tests that can be run quickly in a development environment—can be a valuable tool for improving quality. By erring on the side of testing small, testing more, and testing around the items that are difficult to test directly, you can build reliable software and develop the discipline to test better.
Moving Toward Unit Testing
On the surface, unit testing seems like a simple idea. By testing early and in small parts, you can reduce cycle time for development, improve quality by catching problems early, and enable a whole suite of agile practices, such as refactoring and continuously working software. Like any new practice, unit testing effectively is difficult, and it may take time to demonstrate the value of the additional effort.
If you are starting a new project with a team that is experienced with unit testing and disciplined in the art of test-driven development, seeing the benefits to unit testing is easy. Many teams adopt agile practices while working on legacy code. Unit testing code that has not been designed in a manner that makes unit testing easy can be a challenge. (Michael Feathers, in his book Working Effectively with Legacy Code, defines legacy code as "code without tests.")
Unit testing can also be a cultural challenge. While the team and management might acknowledge the value of unit testing, it is extra work and extra time. At the beginning, unit testing, like any new skill, can slow the team down as team members learn how to test effectively. Once you've overcome the learning curve, the value of early testing manifests itself in fewer problems later on in the process and not seeing a problem is often something people take for granted.
Even if you overcome the cultural hurdles and agree to try unit testing, you may still find yourself trying to decide how best to test. Too much analysis about where to test before you have experience can lead you to test less than you should to realize the benefits of unit testing.
To successfully adopt unit testing as a practice, start with the tests that seem simple, obvious, or even trivial, and you will find that you benefit more that you expect, create a testing culture, and develop instincts that will allow you test more effectively as time goes on.
When faced with the prospect of testing legacy code, you may find yourself with a dilemma. Often, code written without testing in mind is poorly decomposed and the code to set up a test can be very complex, making it difficult to write tests without external resources such as databases. In these cases, it's often useful to start by testing whatever method on a class seems easy to test, and using those tests to give you the confidence to refactor other parts of code to be more testable.
No method is too trivial to start with. I once was on a project where we spent the better part of a day on a problem that was traced to a class that violated something as basic as the equals/hash code contract on a C++ class. Everyone assumed that this was too simple to be worth a test, easy enough to validate by inspection, and not likely to be incorrect as the methods were (initially) generated by the IDE. But, as code evolved, people forgot to review the code to be sure that the methods were in sync. While code reviews, whether through pair programming or a more structured process, can be valuable for identifying unanticipated problems, an automated test is quicker and more reliable than visual inspection for validating that your code honors low-level contracts that you already know about. Tests like these are simple to write and can more than pay for themselves if they prevent one day of your team puzzling over an obscure problem.