How to deal with interface overuse in TDD?

萝らか妹 提交于 2019-12-02 19:03:19

Your tests are telling you to redesign your classes.

There are times when you can't avoid passing complex collaborators that need to be stubbed to make your classes testable, but you should look for ways to provide them with the outputs of those collaborators instead and think about how you could re-arrange their interactions to eliminate complex dependencies.

For example, instead of providing a TaxCalculator with a ITaxRateRepository (that hits a database during CalculateTaxes), obtain those values before creating your TaxCalculator instance and provide them to its constructor:

// Bad! (If necessary on occasion)
public TaxCalculator(ITaxRateRepository taxRateRepository) {}

// Good!
public TaxCalculator(IDictonary<Locale, TaxRate> taxRateDictionary) {}

Sometimes this means you have to make bigger changes, adjust object lifetimes or restructure large swaths of code, but I've often found low-lying fruit once I started looking for it.

For an excellent roundup of techniques for reducing your dependency on dependencies, see Mock Eliminating Patterns.

Don't use interfaces! Most mocking frameworks can mock concrete classes.

That's the drawback of mock based testing approaches. This is as much a test boundary discussion as it is about mocking. By having a 1:1 ratio of test cases to domain classes your test boundary is very small. A result of a small test boundary is a proliferation of interfaces and tests that depend on them. Refactoring becomes more difficult due to the number of interactions you are mocking and stubbing out. By testing clusters of classes with a single test, refactoring becomes easier and you use fewer interfaces. Beware, however that you can test too many classes at once. The more complexity your classes have, the more code paths you need to test. This can lead to a combinatorial explosion and you can't possibly test them all. Listen to the code and tests, they're telling you something about your code. If you see the complexity increasing, it's probably a good time to introduce a new Test Case and Interface/Implementation and mock it out in your original.

If you are feeling uneasy about the number of interfaces being passed into a particular class; then it is probably a sign that you are introducing too many disparate dependencies.

If SomeClass depends on IDependencyA, IDependencyB, and IDependencyC, this is an opportunity to see if you can extract out the logic that the class performs with those three interfaces into another class/interface, IDependencyABC.

Then when you are writing your tests for SomeClass, you only need to mock out the logic that IDependencyABC now provides.

In addition, if you are still uncomfortable; maybe it is not an interface you require. For example, classes that contain state (parameters being passed around, for instance) could probably just be created and passed around as concrete classes instead. Jeff's answer alluded to this, where he mentions passing into your constructor ONLY what you need. This provides less coupling between your constructs and is a better indication of the intent of your class's needs. Just be careful passing around data structures (IDictionary<,>).

In the end, TDD is working when you get that warm fuzzy feeling during your cycles. If you feel uneasy, watch for some of the code smells and fix some of those issues to get back on track.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!