简体   繁体   English

静态工厂方法和模拟

[英]Static factory methods and mocking

How do you reconcile using static factory methods and mocking? 你如何使用静态工厂方法和模拟协调?

Many people would just say: Don't use static factory methods, use DI instead. 很多人只会说:不要使用静态工厂方法,而是使用DI。

Well, sometimes you cannot avoid static factory methods. 好吧,有时你无法避免静态工厂方法。 Consider the following use cases, which should be familiar: 考虑以下用例,应该是熟悉的:

Imagine you have a class called Option , like in scala. 想象一下,你有一个名为Option的类,就像在scala中一样。 You can't avoid using a static factory method if you want to reuse same instance for all absent values. 如果要为所有缺失值重用相同的实例,则无法避免使用静态工厂方法。

As soon as you go new Option(null) you create a new option object, you cannot return the same object over and over again. 一旦你去new Option(null)你创建一个新的选项对象,你就不能一遍又一遍地返回同一个对象。

Similar use case is the Integer.valueOf() which will reuse integer objects for values below 128. Impossible to do without using a static factory method. 类似的用例是Integer.valueOf() ,它将重用128以下的值的整数对象。不使用静态工厂方法是不可能的。

Another advantage is that factory methods are more descriptive than new keyword. 另一个优点是工厂方法比new关键字更具描述性。

So how do you guys deal with having to use static factory methods and at the same time wanting to use inheritance and mocks? 那么你们如何处理必须使用静态工厂方法并同时想要使用继承和模拟?

Thank you. 谢谢。

Mocking out static methods is possible using PowerMock . 使用PowerMock可以模拟静态方法。 Consider the following example from their Wiki page : 从他们的Wiki页面考虑以下示例:

@Test
public void testRegisterService() throws Exception {
    long expectedId = 42;

    // We create a new instance of test class under test as usually.
    ServiceRegistartor tested = new ServiceRegistartor();

    // This is the way to tell PowerMock to mock all static methods of a
    // given class
    mockStatic(IdGenerator.class);

    /*
     * The static method call to IdGenerator.generateNewId() expectation.
     * This is why we need PowerMock.
     */
    expect(IdGenerator.generateNewId()).andReturn(expectedId);

    // Note how we replay the class, not the instance!
    replay(IdGenerator.class);

    long actualId = tested.registerService(new Object());

    // Note how we verify the class, not the instance!
    verify(IdGenerator.class);

    // Assert that the ID is correct
    assertEquals(expectedId, actualId);
}

It's even possible to mock out only one particular method and leave the rest as is, using partial mocking . 甚至可以模拟出一种特定方法,并使用部分模拟将其余方法保留原样。

Since it's a theorical question, I will make a theorical answer. 既然这是一个理论问题,我会提出一个理论上的答案。 The factory paradigm is the building point for another theory: the Injection. 工厂范式是另一种理论的建立点:注射。 If your created objects are injected when needed, then you only have to inject your mocked objects to do all your tests. 如果在需要时注入您创建的对象,那么您只需要注入模拟对象来执行所有测试。 There alot of good books / web pages that can help you to get started on that. 有很多好的书籍/网页可以帮助你开始这个。

My first option is to avoid the need to mock anything, so having static factory methods or not makes no difference. 我的第一个选择是避免模拟任何东西的需要,所以使用静态工厂方法或不是没有区别。

That said, if I do want or need to mock them, then I just do it. 也就是说,如果我确实想要或需要嘲笑它们,那么我就是这样做的。 For example, consider you are testing a JSF-based web application, and you want to mock the javax.faces.context.FacesContext object. 例如,假设您正在测试基于JSF的Web应用程序,并且您想要模拟javax.faces.context.FacesContext对象。 I would write the following in a test, using the JMockit library (which I happen to develop): 我会在测试中使用JMockit库(我碰巧开发)编写以下代码:

@Test
public void exampleTest(@Mocked final FacesContext ctx) {
    // Call the code under test, which will at some point
    // call FacesContext.getCurrentInstance(), then add an
    // error message for display in the web page.

    new Verifications() {{
        FacesMessage msg;
        ctx.addMessage(null, msg = withCapture());

        assertEquals("The expected error message.", msg.getSummary());
        assertNotNull(msg.getDetail());
    }};
}

In this example, Faces.getCurrentInstance() is the static factory method, which will automatically return a mock FacesContext instance once the class is mocked. 在此示例中, Faces.getCurrentInstance()是静态工厂方法,一旦模拟了类,它将自动返回模拟FacesContext实例。

We simply avoid using static factory methods and we use dependency injection instead. 我们简单地避免使用静态工厂方法,而是使用依赖注入。

If java had been designed with DI in mind from the start, then Integer.valueOf() would have been: 如果java从一开始就设计了DI,那么Integer.valueOf()就是:

  • integerType.valueOf() where integerType would be an injected dependency, or integerType.valueOf()其中integerType是注入的依赖项,或

  • typeSystem.getInteger().valueOf() where typeSystem would be an injected dependency, or typeSystem.getInteger().valueOf()其中typeSystem将是一个注入的依赖项,或

  • environment.getTypeSystem().getInteger().getFactory() where environment would be an injected dependency. environment.getTypeSystem().getInteger().getFactory()其中environment是一个注入依赖项。

There is nothing that you can do with static factories that you cannot do with diligent use of dependency injection. 静态工厂无法做任何事情,而勤奋使用依赖注入则无法做到这一点。

Now, once someone makes something available only via a static factory method, they are essentially coercing you to take the static road. 现在,一旦有人通过静态工厂方法提供某些东西,他们实际上就是在强迫你采取静态道路。 This is unfortunate. 这很不幸。 But you can still wrap the static stuff in instances of your own device, and then inject those instances as dependencies into your production code, and then have your unit tests exercise those instances, avoiding the need to do such ungodly hacks as mocking static methods. 但是您仍然可以将静态内容包装在您自己的设备的实例中,然后将这些实例作为依赖项注入到生产代码中,然后让您的单元测试运行这些实例,从而避免像模拟静态方法那样执行此类不合理的操作。

For example, you can wrap System.out in some StandardConsole object implementing some Console interface, and inject that Console interface as a dependency into your application. 例如,您可以在实现某些Console接口的某个StandardConsole对象中包装System.out ,并将该Console接口作为依赖项注入您的应用程序。

(And if you do that, I would even add that you may proceed and configure your version control system to reject any attempts to commit code containing the string "System.out". [evil grin]) (如果你这样做,我甚至会补充说你可以继续并配置你的版本控制系统来拒绝任何提交包含字符串“System.out”的代码的尝试。[evil grin])

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM