Unit testing is valuable technique that can improve software quality. There are however many quality designed software products that have few or no unit tests at all and on the opposite side, there can be as much unit testing code as application code, but that alone doesn't gurantee quality of an overall product. Why is that the case?
Unit test coverage and software quality are not the same. One can have all unit tests passed at the end of the day, see green bar and go home, but code may have serious design flaws. Sometime good design can evolve from doing the simplest thing first but it is not always the case.
Unit testing is simply hard. Once people get past first trivial calculator-style unit tests, they find that often time real applications are not unit testable. These kind of applications have a web of dependencies and no iterfaces to mock. There are of course solutions out of it such as TypeMock or Microsoft Fakes isolation framework as well as many other techiniques described in Michael Feathers book, what I see most often is people writing not unit tests bur rather integration tests against database.
Ideally the majority of tests should be unit tests and not integration tests. A true unit test would test just one feature, maybe a class or a method with depencies mocked. An integration tests runs against a number of components all the way to the database. Integration tests are usually very slow and brittle since they require changing and rolling back database state.
If most of the tests are integration test, they will be too many false positive testing or CI failures. For example, unit test can be written to expect certain database condition or state, but if for a variety of reason database schema or data changes, that unit test will fail.
In pursue of holly grail of 100% unit test coverage a lot of testing infrastracture code should be written. This code has to be maintaned and typically changed when design changes.
I personally try to follow some of these principles in my projects:
- Go after big dogs. Write unit test for the methods with highest cyclomatic complexity. Stuff that has complex logic and potential to fail or something that gets broken often. Don't worry about linear code.
- Write unit tests for every production bug. Duplicate the bug by writing a unit test that fails, then fix the bug and watch unit test passed.
- Structure new code to be unit-tests friendly. I think most of the people are doing it these days.
- Use isolation frameworks such as TypeMock Isolator or Microsoft Fakes / Moles. I have been following Fakes since it started as Moles. It is still a little bit rough and not as polished as TypeMock, but I hope it will get better over time.