One fundamental issue I have with TDD is that the tests are usually written by the same person who does the implementation. The unit tests often are exact implementations of how the object / function should be used. This is acceptable if the complexity of the function is low, but usually bugs are not found in low-complexity functions. Bugs usually come from assumptions about the system, and the programmer would make the exact same assumptions when writing the test.
You could get rid of all assumptions by designing a solid software architecture, but at some point you have to make a trade-off between writing code without assumptions (causing lots of boilerplate code) and writing simple software that just does exactly what you want.
A small example:
void MyMachine::openDoor()
{
hardwareLayer->openDoor();
}
/**
Open the door!
@pre pin 1 must be set to output.
**/
HardwareLayer::openDoor()
{
ASSERT(pinDirection(1) == OUTPUT);
setPin(1, high);
}
TEST(MyMachine, openDoor)
{
EXPECT_CALL(hardwareLayerTest.openDoor());
myMachine.openDoor();
}
TEST(HardwareLayer, openDoor)
{
//Test when pin is input instead of output
hardwareLayerTest.setPinDirection(1, INPUT);
EXPECT_ASSERT(myMachine->openDoor());
//Test when pin is output
hardwareLayerTest.setPinDirection(1, OUTPUT);
EXPECT_CALL(hardwareLayerTest, setPin(1, HIGH));
myMachine->openDoor();
}
The above code yields 100% coverage and 100% test pass, but it still isn't very robust. There won't be a bug in this code, the bug wil manifest itself when somebody during the execution of the program abusively calls setPinDirection(). Even then it still works as intended.
You could design away the setPindirection() function with some clever OO stuff, but how far do you want to go? Do you really want 100 lines of code to open a damn door? This case is relatively easy to make more robust, but things get more complex really fast... Also be aware that adding code to cover a potential bug also may introduce new potential bugs.
I'm not saying unit tests are completely useless, because they aren't. You often write test functions anyway when writing software. But don't consider unit tests with code coverage the holy grail of writing bug-free software.