Had it been easy, the code wouldn’t probably be called “legacy code”… That said, there are a couple of things one can do to start employing TDD in such codebases. First, there needs to be a fair share of dependency breaking so that test doubles stand a fighting chance. Such refactorings are easy and usually quite safe. Second, many enterprise legacy codebases tend to contain many layers that just call other layers and delegate work to the lowest layer. In such cases, using mockist TDD feels quite natural; at least one is able to test drive the interactions.
State-based testing/TDD may be harder because of the sheer number of dependencies everywhere. Still, when adding new code, we should keep smallness in mind and actively work on reducing the number of dependencies.
Lastly, let’s not forget the option of test-driving a brand new replacement for features X, Y, and Z of the legacy code and have it deleted.
Read more about these topics in Developer Testing: Building Quality into Software:
- Chapter 4: Testability from a Developer’s Perspective, pages 51-54
- Chapter 9: Dependencies, pages 119-133
- Chapter 15: Test-driven Development—Mockist Style, pages 213-223