可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm trying out mock/responsibility driven design. I seem to have problems to avoid returning mocks from mocks in the case of objects that need a service to retrieve other objects.
An example could be an object that checks whether the bills from last month are paid. It needs a service that retrieves a list of bills. So I need to mock that billRetrievalService in my tests. At the same time I need that BillRetrievalMock to return mocked Bills (since I don't want my test to rely on the correctness of the Bill implementation).
Is my design flawed? Is there a better way to test this? Or is this the way it will need to be when using finder objects (the finding of the bills in this case)?
side note: althout Bill might be a value object candidate, the wider problem still remains when the collections aren't containing value objects (eg Users).
回答1:
Most of the time, if I need a mock to return another mock, I find a dependency that makes more sense in the other direction. Stated differently, mock-returning-mock usually points to a violation of the Dependency Inversion Principle.
One common exception: a factory that creates objects (as opposed to a "holder" that simply returns the same object each time). If I need to create multiple objects of the same type during my lifetime, then I might need to depend on an ObjectFactory
and invoke #createObject()
, then perhaps set expectations on the Objects. Even so, I would question this. It might be possible for something else one level up the call stack to create Object
s for me and give them to me as needed.
In the ObjectHolder
case, rather than depending on the ObjectHolder
to get the Object
, I prefer to depend on the Object
directly and force my caller to give it to me however it wants. This respects the desirable design property of context independence.
One specific version of this issue is the "Virtual Clock" pattern. Sometimes you need to depend on virtual clock, but often it's better simply to demand a timestamp. ("Instantaneous Request" pattern.)
回答2:
Mock returning mocks is a strong code smell - a possible problem with the design. It could be that the Bills should be immutable values objects which should not be mocked. Or there is some confusion with the design and the responsibilities of the classes.
The book Growing Object-Oriented Software, Guided by Tests and paper Mock Roles, not Objects from the inventors of mock objects are worth reading.
回答3:
Usually when I mock I end up having a triad of objects. The first object would be a coordinator BillsPaidLastMonthCoordinator
this object has two dependencies BillRetrievalService
and BillPaidValidator
.
You would mock the two dependencies and your test would be for the interaction of retrieval and passing bills to the validation. So for this test you will not care what the data is. This helps separate responsibilities. Your original object was responsible for retrieving Bills
and then seeing if it was a isPaid Bill.
With the way you described the problem you can end up is a noisy and brittle test. The brittleness comes from it being able to be broken in two ways.
With the corrdinator, it doesn't have to change if Bill
implementation changes, Just the objects that actually use a Bill
. My 2centavos.
[EDIT]
This is more aligned with using event handlers (the coordinator)
回答4:
As the Way of the Testuvius advices, no principle, however good, should be taken absolutely and so it is also with the rule that you shouldn't need mocks returning mocks, there are cases when this is quite suitable.
As Gutzofter suggests, you could break your object into two, one for the actual validation and another one for retrieval of the bills to validate. The advantage of this "single responsibility only" principle application is that the validator would be more generic and reusable. On the other hand, if you only have this simple use case and no special need for the higher reusability, it's a very pragmatic to keep the retrieval and validation in a single class. Layering, explosion of the number of objects etc. unjustified by a real need and a real benefit, done only for the sake of satisfying an abstract principle, isn't good. You always have to weight the pros and cons and the reality rarely is simple and as beautiful as we would like :-) Great examples of this pragmatic approach are in Adam Bien's Real World Java EE Patterns - Rethinking Best Practices.