One important factor which makes unit tests extremely useful is fast feedback.
Consider what happens when you have your app fully covered with integration/System/functional tests (which is already an ideal situation, far from reality in most development shops). These are often run by a dedicated testing team.
- You commit a change to the SCM repo,
- sometime (possibly days) later the testing guys get a new internal release and start testing it,
- they find a bug and file a bug report,
- (in the ideal case) someone assigns the bug report back to you.
This all may take days or even weeks. By this time you have already been working on other tasks, so you don't have the minute details of the code written earlier in your mind. Moreover, you typically don't even have any direct proof of where the bug actually is, so it takes considerable time to find and fix the bug.
Whereas in unit testing (TDD)
- you write a test,
- you write some code to satisfy the test,
- the test still fails,
- you look at the code and typically you have an "oops" experience in a few seconds (like "oops, I forgot to check for that condition!"), then
- fix the bug immediately.
This all happens in a matter of minutes.
This is not to say that integration/system tests are not useful; they just serve different purposes. With well written unit tests you can catch a large proportion of the bugs in code before they get to the integration phase, where it is already considerably more expensive to find and fix them. You are right that the integration tests are needed to catch those types of bugs which are difficult or impossible to catch with unit tests. However, in my experience those are the rarer kind; most of the bugs I have seen are caused by some simple or even trivial omission somewhere inside a method.
Not to mention that unit testing also tests your interfaces for usability/safety etc., thus giving you vitally important feedback to improve your design and APIs. Which IMHO can considerably reduce the chances of module/susbsystem integration bugs: the easier and cleaner an API is, the less the chance of misunderstanding or omission.
What are you experience with automated unit testing, automated integration testing, and automated acceptance testing, and in your experience what has yielded the highest ROI? and why?
ROI depends on a lot of factors, probably the foremost of them is whether your project is greenfield or legacy. With greenfield development my advice (and experience so far) is to do unit testing TDD style from the beginning. I am confident that this is the most cost effective method in this case.
In a legacy project, however, building up sufficient unit testing coverage is a huge undertaking which will be very slow to yield benefits. It is more efficient to try to cover the most important functionality with system/functional tests via the UI if possible. (desktop GUI apps may be difficult to test automately via the GUI, although automated test support tools are gradually improving...). This gives you a coarse but effective safety net rapidly. Then you can start gradually building up unit tests around the most critical parts of the app.
If you had to pick just one form of testing to automated on your next project, which would it be?
That is a theoretical question and I find it pointless. All kinds of tests have their use in the toolbox of a good SW engineer, and all of these have scenarios where they are irreplaceable.