In this session Michael Feathers basically killed the argument based on; I don’t want tests to drive my design with a simple example base on the code smell; Iceberg class.
Private methods are really hard to test, and here we have a class with a bunch of them, It will be hard to test, but in addition to that it’s also a bad design decision. It violates the single responsibility principle.
A better design would be to break this out in smaller classes, something like;
So why is this true?
There is basically two reasons, related to each other, that drives this synergy. To begin with you are consuming and using the api, which quickly lets you feel the pain. The second reason is based on the fact that test help you understand code. Badly designed code will be hard to test since it’s also very hard to understand what it does. In the light of this, SOLID principles makes a lot of sense. Not only does it allow you to test your code, but it makes it easier to understand.
With these facts, Michael stated a simple truth;
Solving Design Problems
Solves Test Problems
It’s not about letting a test tell you what to do, making code testable does not necessarily imply better design. But better design will make your code easier to test.
This is also why he has problems with “Groping test tools”, tools that let you test private parts. He feels they are a “Get out of jail free card”. Which removes an important force to make the design better; Pain. Pain is useful as a learning tool in that it tells you what to avoid. Sure removing test-pain will make the code easier to test, but will it make it easier to maintain or extend? Usually not.
He then concluded that testing isn’t hard, testing is easy in the presence of good design.
This was a great talk! By examining relationship between test-pains, code smells and design principles, Michael made a clear point that there is a synergy. If you find it hard to test your code, there is usually some other problem with it.
He didn’t want to impose TDD on everyone, but he made his case that test will help you understand your code better and act as a constraint that forces you into good design. Something you should be doing anyway.
Catalogue of testing pains and relation to code smells
|State hidden within a method – you find yourself wishing you could access local Variables in a test.||Methods are typically to long and are violating Single Responsibility Principle.|
|Difficult Setup – Instantiating a class involves instantiating 12 others.||This pain says something about the design, it means that the class isn’t factored into smaller pieces. Problem: To Much coupling.|
|Incomplete shutdown – pieces of your code lose resources and you never realize it until you test.||Classes don’t take care of themselves, poor encapsulation.|
|State-leaks across tests – The state of one test affects another.||Smells of improper use of sIngletons or other forms of global mutable state.|
|Framework Frustration – Testing in the presence of a framework is hard||Insufficient Domain separation. Lacking good separation of concerns.|
|Difficult mocking – You find yourself writing mocks for objects returned by other objects||Law of Demeter Violations|
|Difficult mocking 2 – hard to mock particular classes||Insufficient abstraction and / or dependency inversion violation.|
|Hidden effects – you can’t test a class because you have no acess to the ultimate effect ot its execution||Insufficient separation of concerns and encapsulation violation|
|Hidden inputs – There is no way to instrument the setup conditions for a test through the API||Over encapsulation, insufficient separation of concerns|
|Unwieldy parameter lists – it’s too much work to call a method or instantiate a class||To many responsibilities in a class or a method. SRP violation|
|Insufficient access – you find yourself wishing you could test a private method.||To many responsibilities in a class. SRP violation|
|Test Trash – many unit tests change whenever you change your code.||Open/Closed violations|