Mark Ploeh has an interesting post about interfaces in TDD – that interfaces aren’t necessarily abstractions. That’s certainly true. Interfaces don’t guarantee we’re actually following SOLID design principles. In fact, the whole idea of the typical repository pattern in a lot of “DDD” codebases just screams of ISP violations.
However, I don’t think any of that changes the value of using interfaces in TDD. Interfaces let me do a few things that are annoying, difficult or impossible with concrete/abstract classes
- Do top-down design
- Program to implementations that don’t exist
- Test against dummy/mock/fake/stub implementations
C# isn’t a dynamic language. I can’t easily swap out behavior at runtime, (or at least, I don’t want to pay for a tool that lets me). Interfaces are the easiest way for me to accomplish my goals in TDD, even if I have the issue of 95% of the interfaces I create only having 1 implementation.
All the points Mark made in his post:
- LSP violations
- ISP violations
- Shallow interfaces
None of these go away when I choose classes. Instead, TDD just becomes harder. Now, I do have a lot more end-to-end, behavioral tests these days as I find these have more value. But interfaces still provide the clearest definition to end users on the needs of the class’s dependencies. Interfaces in the constructor tell us that this class needs an ISomething to accomplish its task, but concrete Something says it needs THIS SPECIFIC Something implementation to do its work.
So no, interfaces aren’t compiler-enforceable contracts, but our usage with proper naming and tests does lead us down this path.