可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I have started using Guice to do some dependency injection on a project, primarily because I need to inject mocks (using JMock currently) a layer away from the unit test, which makes manual injection very awkward.
My question is what is the best approach for introducing a mock? What I currently have is to make a new module in the unit test that satisfies the dependencies and bind them with a provider that looks like this:
public class JMockProvider<T> implements Provider<T> {
private T mock;
public JMockProvider(T mock) {
this.mock = mock;
}
public T get() {
return mock;
}
}
Passing the mock in the constructor, so a JMock setup might look like this:
final CommunicationQueue queue = context.mock(CommunicationQueue.class);
final TransactionRollBack trans = context.mock(TransactionRollBack.class);
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
protected void configure() {
bind(CommunicationQueue.class).toProvider(new JMockProvider<QuickBooksCommunicationQueue>(queue));
bind(TransactionRollBack.class).toProvider(new JMockProvider<TransactionRollBack>(trans));
}
});
context.checking(new Expectations() {{
oneOf(queue).retrieve(with(any(int.class)));
will(returnValue(null));
never(trans);
}});
injector.getInstance(RunResponse.class).processResponseImpl(-1);
Is there a better way? I know that AtUnit attempts to address this problem, although I'm missing how it auto-magically injects a mock that was created locally like the above, but I'm looking for either a compelling reason why AtUnit is the right answer here (other than its ability to change DI and mocking frameworks around without changing tests) or if there is a better solution to doing it by hand.
回答1:
You shouldn't need to inject mocks though a DI framework. I'm using Guice and JMock quite successfully and my unit tests don't reference anything Guice related. I only use mocks and pass in null
where applicable.
DI will allow the injection and construct of the dependencies of the current class, so If you want to add a mocked out class (which effectively stops the dependency graph) you just need to pass it in. Misko Hevery stated in one of the Google Tech Talks that unit tests should be littered with new
's and null
's because their scope is localized to the individual unit test method - I have to agree with him.
Is there a reason to need to use Guice in the tests, that is if they are not functional / integration tests?
Wouldn't the test work if it excluded the injector? Couldn't you refactor your test to something like:
final CommunicationQueue queue = context.mock(CommunicationQueue.class);
final TransactionRollBack trans = context.mock(TransactionRollBack.class);
context.checking(new Expectations() {{
oneOf(queue).retrieve(with(any(int.class)));
will(returnValue(null));
never(trans);
}});
RunResponse r = new RunResponse(queue, trans); // depending on the order
r.processResponseImpl(-1);
回答2:
From what you said, it sounds like you are not doing a real unit-test. When doing unit-test for a class, you only focus on a single class. When you execute your unit-test, the execution should only exercise the subject class to be tested and avoid running methods from related classes.
JMock/Easymock are tools to help achieve unit-testing your class. They are not useful in integration test.
In the context of unit-testing, Guice is overlapped with JMock/Easymock. Guice forces users to pollute their code with all kind of annotations (such as @Inject) to achieve the same result as JMock/Easymock. I really strongly disagree with Guice in the way they help in testing. If developers find themselves to use Guice instead of JMock/Easymock, they properly either not doing a real unit-test or they try to test controller classes.
Testing controller class should not be done at unit-test level but at integration test level. For integration test, using Jakarta Cactus is much helpful. At integration test, developers should have all dependencies available and hence, there is no need to use Guice.
As a conclusion, I don't see any reasons to use Guice in testing a product. Google Guice might be useful in different contexts but testing.
回答3:
Yishai, I have to correct that driving a better design should be credited to Dependency Injection pattern introduced by Martin Fowler. Guice is a tool to help if your codes are already followed DI pattern. Guice doesn't contribute anything to your better coding. Guice is solving DI in ad-hoc manner (at annotation level), so it should not be considered to be used as DI framework either. This is the big difference between xml based DI container by Spring and annotation based DI container by Guice.
I spent some time to read your code posted at the beginning (I usually don't) just to understand why you said unit test your class isn't possible. I finally understand the problem you are facing. From what I see is at unit-test level, you attempted to mimic the execution flow of the piece of code when it runs inside Java EE server. That's the reason you got stuck. I don't know what the incentive here is but I am afraid that your effort of preparing all of these environmental dependencies is going to be wasted :(
Writing unit test is to test a "business logic" which is independent from whatever technology you are using. There might be an aside requirement stating that this business logic must be executed within Quality of Service (QoS) environment. This requirement is "technical requirement". In your example, CommunicationQueue and TransactionRollBack are mostly functioning to satisfy the "technical requirement". The situation is your "business requirement" is being surrounded by all of these environmental units. What you should do is refactoring your code so that your "business requirement" is in separated methods which have no dependencies. Then you write unit-test for these methods.
I still consider your are doing functional testing. Doing functional testing at unit-test level is very time-consuming and it is not worth to do such thing either. I hope it helps
回答4:
Yishai, sounds like you need AOP to walk around these dependencies. AspectJ and AspectWerkz are both useful resources. However, AspectWekz has a very nice feature of weaving aspect classes at runtime using JVM hotswap technology. Of course, these engineering effort should only apply at unit testing level.
I have put lot of effort in resolving dependencies when doing unit testing. Tools such as JMock, EasyMock, Java EE micro-container, AspectJ, AspectWerkz, Spring IoC can be used to solve DI issue. None of these tools are driving developers putting testing awareness in their production codes. Too bad that Guice is violating the concept of keeping your codes clean from any sorts of testing awareness. And I don't count it as a tool to be used in my unit-test.
I hope I have given you enough reasons to take @Inject annotation off your production code.