Refactoring poorly written code that has little abstraction and does not separate concerns can greatly reduce the risk of defects and speed up future development. Adding unit tests during this small scale redesign process can have all the benefits of unit testing during the initial implementation.
Adding unit tests to poorly written code without any refactoring achieves none of this. Instead it acts as a barrier to refactoring and improving code. If a method or class serves multiple purposes and abstracts no underlying complexities then it serves as an entry point for defects and often expands the cost of adding or refining features. Unit testing such a method or class makes it more difficult to refactor and remove these deficiencies.
Lastly passing unit tests for such code do not provide confidence that the code is working as it should. The passing tests instead act as a false positive. If the code has no clear definition of what it should do then a test that says it is doing what it should does not help.
I could test the ability of a hammer to dig a hole and then use that hammer turn a screw. In this scenario my test gives me false confidence in the ability of the hammer to the job for which it is being used. All the while the intended usage of the hammer is missed. The real answer to this scenario is to use a screw driver. This would be similar to refactoring code. Then test the ability of the screw driver to screw in screws. This would be similar to unit testing.
Check out my blog for more of my musings upon technology and various other topics.
Top comments (5)
I usually go the other way around: uncle Bob said that "refactoring represents small changes in the code that keep the tests passing". With that in mind, we should never start refactoring without having the code covered.
I would agree in the situation where the units of code that you are testing have some minimum amount of encapsulation and separation of concerns. If however you would need to refactor your tests in order to begin refactoring your code then at least some code refactoring would need to come first. However most of the time what you described would be the preferred scenario to be in but I was wanting to highlight a scenario that does not fit into the normal pattern of refactoring and unit testing.
If refactoring breaks your tests means either your tests are fragile or your code is not SOLID. Let's not confuse refactoring with change requests
Sometimes it is the interaction and interface between functions and classes that need refactored. It is of no benefit to add a unit test to an interface which does not isolate concerns, provide abstraction, or encapsulation only to then refactor that interface.
Exactly.. if the code follows the SOLID principles, it's easier to write tests that won't break at the smallest code change.