But unit testing can be a drag when you start, and it adds considerable time to a project. It can be tempting to just test particularly complex or obscure units, and doing it like that might be right for you - it's better than not doing it at all.
Yes, and unit testing starts paying off right at the moment when someone changes something in the existing code. After any changes, it is typically required to test whether the new changes has introduced some regression in the existing code base (ie. did the new changes brake anything in the existing unit test cases). Adding new features requires writing and maintaining the unit test cases, of course. But the payoff is that after each change there is a way to run unit tests automatically, and find any problems (which are covered by the unit tests, of course) at the very earliest state of development. Alternative method for automated unit testing is manual testing for each unit after modifications. And this will be a real drag in the development cycle.
Yes. Now the question of course is who will write the unit tests. Ideally not the same person as the one who wrote the code being tested. In that regard, unit testing for regression testing is good, but if the persons making modifications to the code are allowed to make modifications to the tests themselves, that's a bit pointless. And yes, I've seen that happen.
As I have understood, in some high-reliability projects it is
required that someone else than the original author is writing the unit tests. In normal, less-critical projects it is typical that the original author/authors are writing and maintaining the unit tests as the code is evolving.
There are two aspects in unit testing: white box testing where you can see inside the code and its implementation, and black box testing in which you just see the interfaces and test the code using these interfaces only. In some cases it is beneficial to use external developers for writing unit tests for the interfaces, as this will provide good feedback how well the interfaces are designed and documented, and how robust your code is for illegal parameters and abuse.
Well-written unit tests are always welcome, because they help with regression and sanity tests. Poorly written unit tests should be removed altogether from any project because they just introduce false sense of code quality.
Maintaining the unit tests is another story. As the code evolves, some features are added or changed, the unit tests need to be updated accordingly. If the unit of code to be tested is a monolithic blob of code or very complicated, the maintenance may become an issue. Thus, it is recommended to keep the units of code as small and independent as possible, so that it is practical to maintain unit tests during product's lifetime. It is much easier to test a well defined code with clear interface, functionality and intention, than some obscure blob of code.
If writing unit tests for a particular module looks very hard, it is typically an indication that the code needs refactoring in the first place. In this regard unit tests may also help with improving the overall design & architecture. If developers know that they need to be able to write unit tests for the code they produce, they probably tend to write code that is simple, clean and to easy to understand and maintain.
About unit test frameworks: Try to use any of the well-known, popular, successful unit test framework that suit your needs. In this way the new developers are able to find documentation for the unit test framework, and you do not have to spend time for tutoring, supporting and writing the documentation for the framework per se.
Personally I am having a habit of adding lots of compile-time static asserts, run-time asserts, verify-macros and precondition/postcondition-macros in my code [where possible]. The run-time checks can be enabled/disabled from the project's Makefile(s) per project or per unit. These checks are always enabled during unit testing. Of course, it is important to understand what side-effects mean in this context, and write these checks so that they do not introduce any side-effects. These asserts and other run-time macros have helped with catching hard to find run-time issues in the code, ie. thos wtf-moments. They also help in catching illegal function argument values or corrupted data structures etc. Using these macros it is possible to implement
programming by contract quite easily.