The book is a strong proponent of the collaboration verification style of unit testing even when it comes to communication between individual objects inside the domain model. In my opinion, it’s the main shortcoming of the book. All other shortcomings flow from this one.
To justify this approach, the authors allude to the definition of Object-Oriented Design given by Alan Kay:
“The big idea is “messaging” […] The key in making great and growable systems is
much more to design how its modules communicate rather than what their internal
properties and behaviors should be.”
They then conclude that interactions between objects is what you should focus on foremost in unit tests. By this logic, the communication pattern between classes is what essentially comprises the system and identifies its behavior.
There are two problems with this viewpoint. First, I wouldn’t bring the Alan Key’s definition of OOD here. It’s quite vague to build such a strong argument upon and has little to do with how modern strongly-typed OOP languages look like today.
Here’s another famous quote of him:
“I made up the term ‘object-oriented’, and I can tell you I didn’t have C++ in mind”.
And of course, you can safely substitute C++ with C# or Java here.
The second problem with this line of thinking is that separate classes are too fine-grained to treat them as independent communication agents. The communication pattern between them tend to change often and has little correlation with the end result we should aim at verifying.
As I mentioned in the previous post, the way classes inside the domain model talk to each other is an implementation detail. The communication pattern only becomes part of API when it crosses the boundary of the system: when your domain model starts interacting with external services. Unfortunately, the book doesn’t make this distinction.
The drawbacks of the approach the book proposes become vivid when you consider the sample project it goes through in the 3rd part. Not only focusing on collaboration between classes entails fragile unit tests that couple to the SUT’s implementation details, but it also leads to an overcomplicated design with circular dependencies, header interfaces, and an excessive number of layers of indirection."
There's right and wrong in everything. It would be nice to figure out what are nuggets of food for thought.
For example, I am also pretty wary of testing internal collaborations or implementations eg via mocks.
Not that it is right or wrong, but that it has pluses and minuses. (And each of those can get a 5whys, too.)
How could / should we not also think about how we could grow oo software with less 'coupling'?