“I don’t need tests: tests are for people who write bugs.” –@hipsterhacker
Most of the value of a unit test comes when you change the original (tested) code, but not the test, and can run the test to make sure that all is still well. The tested code must satisfy two requirements for this value to be realized:
The code must have a small, stable API.
The code must be complex enough to break easily.
Most classes do not satisfy both of these, and should not be unit tested.
For an example of code that violates the first requirement, consider complex code that orchestrates various services. It pulls data from some, passes to others, and has various logic internally. The API here is complex. (I’m counting its outgoing calls as part of its API in the sense that the test must verify that the calls are correct.) Most changes to the code will also include changes to outgoing calls. You can write mock implementations of these services, but you’ll have to change the mocks when anything is changed about the class. Then in what sense is the test really helpful? It’s really just implementing the class a second time in a different way. It’s not verifying that the implementation is correct. It’s only verifying that the two implementations match.
For an example of code that violates the second requirement, look at the classic example of a unit test for a stack class. A stack has a very simple API, one that’s practically defined by the term “stack”. It’s straightforward to test a stack’s API, verify last-in-first-out, and so on. That’s why stacks are so often used as unit test examples. But so what? How likely is your stack implementation to break after it’s initially written?
For an example of code that’s perfect for unit testing, consider a date parser. (In fact all text parsers, including compilers, are ideal candidates for unit testing.) There’s a set of date formats that it can parse, like “January 1, 2009” and “01-JAN-09” and it must return a canonical object representing the date. That’s a very small, well-defined API that won’t change. (It can’t change because you must parse all the dates you’ve correctly parsed before.) If you find a bug, you can add it to your test, fix the code, and verify that no previously-parsed dates fail. A text parser has a simple API and often has sufficiently-complex logic to warrant a unit test.
For other types of code, your time is better spent carefully re-reading your code or having it reviewed by a peer.