【问题标题】:What is the best way to use Guice and JMock together?一起使用 Guice 和 JMock 的最佳方式是什么?
【发布时间】:2013-05-26 18:19:46
【问题描述】:

我已经开始使用 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 和模拟框架的能力)或者是否有更好的解决方案来手动完成。

【问题讨论】:

    标签: java guice jmock


    【解决方案1】:

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

    DI 将允许注入和构造当前类的依赖项,所以如果你想添加一个模拟出来的类(这有效地停止了依赖关系图),你只需要传入它。Misko Hevery 在其中之一中声明Google Tech Talks 认为单元测试应该充斥着newnull,因为它们的范围仅限于单独的单元测试方法——我必须同意他的观点。

    是否有理由需要在测试中使用 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);
    

    【讨论】:

    • 在具体示例中,模拟不需要 DI 是非常正确的(如果我将构造函数包设为私有,我猜),但是从这里我有需要注入两个依赖项远离测试代码(RunResponse 从队列中获取一个类名并实例化它),所以我正在寻找 Guice 来为此目的进行字段注入。
    • 我明白了。没有什么能阻止您在测试中使用 Guice,但如果可能的话,我建议您尽量避免使用它(除非您在功能/集成级别上进行测试)这样做的问题是您违反了 Demeter 法则。您当前的类应该只担心它的直接依赖关系,而不应考虑传递依赖关系。在当前类依赖项的单元测试中,您所做的完全相同,并且只考虑直接依赖项。这样的模式让你的代码更简洁,对 DI 更友好 :)
    【解决方案2】:

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

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

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

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

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

    【讨论】:

    • 谢谢 Thang,这基本上就是我现在所处的位置,在大多数情况下我不使用 Guice 进行测试。我发现 Guice 推动了更好的设计,所以它肯定会赢得它的保留。然而,在我的例子中,“一个单元测试只测试一个类”的原则是不可能的,因为一个类需要通过框架来实例化另一个类。为什么会出现这种情况很复杂,但是没有好的方法(比如没有其他影响生产稳健性的缺点)——我已经尝试过了。
    【解决方案3】:

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

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

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

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

    【讨论】:

    • 关于 Guice,我认为注释方法更好,因为它改进了静态检查和减少重复,但我同意 Guice 是一种设计概念的工具。关于我为什么需要这个,评论太长了,但很短,代码要求容器在中间,所以我试图在没有容器的情况下对代码进行单元测试(调用一个方取回结果),但是它们有不同的依赖关系,必须在不同的类中,并且每个都必须通过框架代码实例化。
    【解决方案4】:

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

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

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

    【讨论】:

    • Thang,我不知道为什么你认为 Guice 让你的代码比 Spring、AspectJ 或 AspectWekz 更“具有测试意识”,但你没有给我任何理由,只是替代方案和断言。
    • AspectJ 和 AspectWekz 正在字节码级别处理 DI 问题。 AspectJ 正在使用“ajc”编译器将方面行为注入字节码类。 AspectWekz 通过使用热交换字节码技术在不需要 ajc 的情况下走得更远。这两个工具工作在预编译时、编译时或类加载时执行注入。 Spring 使用基于 xml 的 DI 在更高级别上工作。当然,你的项目必须已经采用 Spring 才能利用这个优势(比如通过工厂获取实例)
    猜你喜欢
    • 2011-04-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-15
    • 1970-01-01
    • 2014-09-17
    • 2011-12-10
    • 2016-04-12
    相关资源
    最近更新 更多