Also doesn't this contradict the major push for tons of unit tests and that in general the more unit tests the better because they help us identify the specific problem when they fail rather than being omnibus?
It sort of does, yes, but this is not necessarily a bad thing.
One of the key benefits of TDD is that it not only helps you define your contracts but also what is meaningful as a "unit."
You write a test for a particular piece of functionality at a particular level, covering whatever you feel is a meaningful behaviour.
If you overfit your tests - as the above mantra would have you - not only do you end up with tests that really don't describe anything meaningful, you also end up with code that is very difficult to refactor and reorganise because every small change in production code ends up a breaking change for tests.
So, let the internals be the internals - that might be a method, a class, or a cluster of cohesive classes. How do you define which? You don't. You find where you feel is a comfortable starting point and you write a test that outlines the first thing you want to do.
Then you go with the flow.
As a unit grows, you might find you need to add some abstraction or reorganise the internals of the unit in order to extend the behaviour to satisfy some new tests. Fine - go ahead. Add some classes, extract some interfaces. You'll know if you've broken anything because your tests are still green.
And if you have a problem that is more granular in scope (e.g. the higher level test is going red under certain conditions due to some edge case in a lower-level component), you can of course go ahead and write a more granular test - scope the test to what you care about as the implementor.
The thing to bear in mind is that any entry point for a test fixture is a hard boundary that will be difficult to change. Anything that isn't a hard boundary is trivial to change.
Remember that the goal of TDD is not to produce a test suite - that is a nice side-effect. The goal of TDD is to help you write working code.
As an aside, this kind of approach is works very will with the "tell, don't ask" principle - you pass your output mechanism into the "unit" at the top-level and assert everything against a fake output implementation:
VendingMachine vendingMachine = new VendingMachine();
vendingMachine.insertCoins(5);
vendingMachine.displayTo(fakeDisplay);
assertThat(fakeDisplay.toString()).isEqualTo("5");
As you add more complex behaviours, you still interact with the VendingMachine
interface with your tests and assert against the display but, internally, you might have more composition going on - the display might get passed around to more specialised subcomponents etc. etc.