I used to unit test all the components individually. But these days I test with most of the componentry wired up, only mocking the i/o layers. I end up writing fewer tests, they have been less brittle, and refactoring is easier.
How does this show you "issues in the code base's architecture?"
In the end you'll have an implementation of the interface with quite complex logic in the methods' implementation to handle differing returns, which will couple your mock implementation to the test cases setup, or you'll reimplement a mocking framework.
I prefer to use a mocking framework to deliver stuff instead of stabbing myself to keep purity of interface-based APIs.
If all the public interface test cases pass, isn't that enough?
But even deeper, it is "accidental" because you are not testing contracts on the public APIs, you're testing the private functions through a layer higher. When the implementation changes, you have coupled your tests so tightly to the implementation that they are useless for regression testing. If not, then you actually aren't testing the private function at all, you're just hitting some parts by coincidence.
Sometimes there are well-defined processes for performing a task, and that task is performed in only one place in the system. Therefore the details of the process can be kept class private inside of the only consumer. That doesn't mean that the processes should never be tested. If the task is cumbersome to set up or runs slowly then there is good reason to test the internal parts of the process which can be tested with dozens, hundreds or even thousands of permutations of input data cheaply and efficiently. Always relying on the large-scale tests to hit every combination of inputs for a well understood subroutine can be inefficient.
You could make the argument that this well-understood process could be broken out into its own class/package/module and tested with its own public interface, but if there really is only one consumer then that's kind of a strange trade-off to make in many cases.
That's how I develop in general: a "component" does not exist because it has multiple-clients, but because it is a conceptual piece of logic that makes sense to document and test in isolation. It allows to define what is the public API of this component and what isn't. This is how software scales and stays maintainable over time IMO.
When I see tests that are orders of magnitude larger than the units they’re testing and contain mocked references that are used 7 stack frames deep I know the author had a fundamental misunderstanding of how to write helpful and maintainable tests.
He was right to change his mind about the other things.
I was also skeptical about remote working, and while I think it is worse in most ways than office working, it's not a lot worse, and the lack of commute is sooo big a benefit that overall it's totally viable.
When I did C++ programming, I would test via public interfaces. If it didn't seem sufficient, or got messy, the reason would always be "This is a big class that is doing a lot of stuff." I would then identify all the things the class was doing, make classes for each one, and have instances of them in the original class as private members.
This way I could test the individual classes easily, and still test the (formerly) big class using only the public interface (with appropriate mocking as needed).
The main thing I learned from unit testing is to pay little attention to the word unit. Insisting on testing the private methods after the exercise above is usually a symptom of aiming for a level of intellectual purity that does not benefit the code.
(Of course, a better solution may be not to use OO to begin with, but that's a whole other discussion).
I worked in a place that disallowed friend functions and classes generally, but explicitly for unit testing. I never wrote another private function. Instead, there was an explosion at the tiny-free-function-that-could-have-been-a-private-function factory, and the parts ended up everywhere.