The purist answer is that private methods are called that for a reason! ;-)
Turn the question around: given only the specification of a (publicly-accessible) interface, how would you lay out your test plan before writing the code? That interface describes the expected behavior of an object that implements it; if it isn't testable at that level, then there's something wrong with the design.
For example, if we're a transportation company, we might have these (pseudo-coded) interfaces:
CapitalAsset {
Money getPurchaseCost();
Money getCurrentValue();
Date whenPurchased();
...
}
PeopleMover {
Weight getVehicleWeight();
int getPersonCapacitly();
int getMilesOnFullTank();
Money getCostPerPersonMileFullyLoaded(Money fuelPerGallon);
...
}
and might have classes including these:
Bus implements CapitalAsset, PeopleMover {
Account getCurrentAdvertiser() {...}
boolean getArticulated() {...}
...
}
Computer implements CapitalAsset {
boolean isRacked() {...}
...
}
Van implements CapitalAsset, PeopleMover {
boolean getWheelchairEnabled() {...}
...
}
When designing the CapitalAsset
concept and interface, we should have come to agreement with the finance guys as to how any instance of CapitalAsset
should behave. We would write tests against CapitalAsset
that depend only on that agreement; we should be able to run those tests on Bus
, Computer
, and Van
alike, without any dependence on which concrete class was involved. Likewise for PeopleMover
.
If we need to test something about a Bus
that is independent from the general contract for CapitalAsset
and PeopleMover
then we need separate bus tests.
If a specific concrete class has public methods that are so complex that TDD and/or BDD can't cleanly express their expected behavior, then, again, that's a clue that something is wrong. If there are private "helper" methods in a concrete class, they should be there for a specific reason; it should be possible to ask the question "If this helper had a defect, what public behavior would be affected (and how)?"
For legitimate, inherent complexity (i.e. which comes from the problem domain), it may be appropriate for a class to have private instances of helper classes which take on responsibility for specific concepts. In that case, the helper class should be testable on its own.
A good rule of thumb is:
If it's too complicated to test, it's too complicated!