7

我已经开始使用 Guice 对项目进行一些依赖注入,主要是因为我需要在远离单元测试的一层注入 mocks(目前使用 JMock),这使得手动注入非常尴尬。

我的问题是引入模拟的最佳方法是什么?我目前拥有的是在单元测试中创建一个满足依赖项的新模块,并将它们与如下所示的提供程序绑定:

public class JMockProvider<T> implements Provider<T> {
    private T mock;

    public JMockProvider(T mock) {
        this.mock = mock;
    }

    public T get() {
        return mock;
    }
}

在构造函数中传递模拟,因此 JMock 设置可能如下所示:

    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);

有没有更好的办法?我知道 AtUnit 试图解决这个问题,虽然我错过了它如何自动神奇地注入一个像上面一样在本地创建的模拟,但我正在寻找一个令人信服的理由,为什么 AtUnit 是这里的正确答案(其他比它在不更改测试的情况下更改 DI 和模拟框架的能力)或者是否有更好的解决方案可以手动完成。

4

4 回答 4

12

您不需要通过 DI 框架注入模拟。我非常成功地使用了 Guice 和 JMock,并且我的单元测试没有引用任何与 Guice 相关的内容。我只使用模拟并在null适用的情况下传入。

DI 将允许注入和构造当前类的依赖项,所以如果你想添加一个模拟出来的类(这有效地停止了依赖关系图),你只需要将它传递进去。Misko Hevery 在 Google Tech 的一篇文章中说单元测试应该充斥着new's 和null's 的谈话,因为它们的范围仅限于单个单元测试方法——我必须同意他的观点。

是否有理由需要在测试中使用 Guice,即如果它们不是功能/集成测试?

如果排除喷油器,测试会不会起作用?您不能将您的测试重构为:

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);
于 2010-01-12T05:29:41.060 回答
1

从您所说的来看,听起来您并没有进行真正的单元测试。在为一个类进行单元测试时,你只关注一个类。当您执行单元测试时,执行应该只执行要测试的主题类,并避免运行相关类的方法。

JMock/Easymock 是帮助实现对你的类进行单元测试的工具。它们在集成测试中没有用。

在单元测试的上下文中,Guice 与 JMock/Easymock 重叠。Guice 强制用户使用各种注释(例如@Inject)来污染他们的代码,以达到与 JMock/Easymock 相同的结果。我真的非常不同意 Guice 帮助测试的方式。如果开发人员发现自己使用 Guice 而不是 JMock/Easymock,他们要么不进行真正的单元测试,要么尝试测试控制器类。

测试控制器类不应在单元测试级别进行,而应在集成测试级别进行。对于集成测试,使用 Jakarta Cactus 很有帮助。在集成测试中,开发人员应该拥有所有可用的依赖项,因此无需使用 Guice。

作为结论,我认为没有任何理由使用 Guice 来测试产品。Google Guice 可能在不同的环境中有用,但在测试中。

于 2010-04-05T05:17:25.043 回答
0

Yishai,我必须更正一下,推动更好的设计应该归功于 Martin Fowler 引入的依赖注入模式。如果您的代码已经遵循 DI 模式,Guice 是一个可以提供帮助的工具。Guice 对您更好的编码没有任何贡献。Guice 正在以 ad-hoc 方式(在注释级别)解决 DI,因此也不应将其视为 DI 框架。这是 Spring 的基于 xml 的 DI 容器和 Guice 的基于注释的 DI 容器之间的巨大区别。

我花了一些时间阅读您在开始时发布的代码(我通常不会)只是为了了解您为什么说单元测试您的课程是不可能的。我终于明白你面临的问题了。从我看到的单元测试级别来看,您试图模仿这段代码在 Java EE 服务器中运行时的执行流程。这就是你被卡住的原因。我不知道这里的动机是什么,但我担心你准备所有这些环境依赖项的努力将被浪费:(

编写单元测试是为了测试一个独立于您使用的任何技术的“业务逻辑”。可能还有一个要求,说明此业务逻辑必须在服务质量 (QoS)环境中执行。这个要求就是“技术要求”。在您的示例中,CommunicationQueueTransactionRollBack主要用于满足“技术要求”。情况是您的“业务需求”被所有这些环境单元所包围。您应该做的是重构您的代码,以便您的“业务需求”在没有依赖关系的单独方法中。然后为这些方法编写单元测试。

我仍然认为您正在进行功能测试。在单元测试级别进行功能测试非常耗时,也不值得这样做。我希望它有帮助

于 2010-04-09T02:53:24.977 回答
0

Yishai,听起来你需要 AOP 来绕过这些依赖项。AspectJ 和 AspectWerkz 都是有用的资源。然而,AspectWekz 有一个非常好的特性,即使用 JVM 热交换技术在运行时编织切面类。当然,这些工程工作应该只适用于单元测试级别。

在进行单元测试时,我付出了很多努力来解决依赖关系。JMock、EasyMock、Java EE 微容器、AspectJ、AspectWerkz、Spring IoC 等工具可用于解决 DI 问题。这些工具都没有促使开发人员在他们的生产代码中加入测试意识。太糟糕了,Guice 违反了让你的代码远离任何类型的测试意识的概念。而且我不认为它是在我的单元测试中使用的工具。

我希望我已经给了您足够的理由从您的生产代码中删除 @Inject 注释。

于 2010-04-11T04:32:18.663 回答