静态工厂方法和模拟

时间:2015-12-01 16:26:00

标签: java unit-testing testing tdd

如何使用静态工厂方法和模拟协调?

很多人只会说:不要使用静态工厂方法,而是使用DI。

好吧,有时你无法避免静态工厂方法。考虑以下用例,这些用例应该是熟悉的:

想象一下,你有一个名为Option的类,就像在scala中一样。如果要为所有缺失值重用相同的实例,则无法避免使用静态工厂方法。

一旦你new Option(null)创建一个新的选项对象,你就不能一遍又一遍地返回同一个对象。

类似的用例是Integer.valueOf(),它将重用整数对象的值低于128.不使用静态工厂方法就不可能。

另一个优点是工厂方法比new关键字更具描述性。

那么你们如何处理必须使用静态工厂方法并同时想要使用继承和模拟?

谢谢。

4 个答案:

答案 0 :(得分:2)

使用PowerMock可以模拟静态方法。请考虑以下Wiki page中的示例:

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

甚至可以使用partial mocking模拟出一种特定方法,其余部分保留原样。

答案 1 :(得分:1)

既然这是一个理论上的问题,我会提出一个理论上的答案。工厂范式是另一种理论的建立点:注射。如果在需要时注入您创建的对象,那么您只需要注入模拟对象来执行所有测试。有很多好的书籍/网页可以帮助你开始这个。

答案 2 :(得分:1)

我的第一个选择是避免模拟任何东西,所以使用静态工厂方法不会产生任何影响。

那就是说,如果我想要或需要嘲笑它们,那么我就是这样做的。例如,考虑您正在测试基于JSF的Web应用程序,并且您想要模拟javax.faces.context.FacesContext对象。我会在测试中使用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());
    }};
}

在此示例中,Faces.getCurrentInstance()是静态工厂方法,一旦模拟了类,它将自动返回模拟FacesContext实例。

答案 3 :(得分:0)

我们只是避免使用静态工厂方法,而是使用依赖注入。

如果java从一开始就设计了DI,那么Integer.valueOf()就是:

  • integerType.valueOf()其中integerType是注入的依赖项,或

  • typeSystem.getInteger().valueOf()其中typeSystem是注入的依赖项,或

  • environment.getTypeSystem().getInteger().getFactory()其中environment是注入的依赖项。

使用依赖注入时,您无法使用静态工厂做任何事情。

现在,一旦有人通过静态工厂方法提供某些东西,他们实际上就是在强迫你采取静态道路。这很不幸。但是您仍然可以将静态内容包装在您自己的设备的实例中,然后将这些实例作为依赖项注入到生产代码中,然后让您的单元测试运行这些实例,从而避免像模拟静态方法那样执行此类不合理的操作。

例如,您可以将System.out包装在实现某个StandardConsole接口的某个Console对象中,并将该Console接口作为依赖项注入您的应用程序。

(如果你这样做,我甚至会补充说你可以继续并配置你的版本控制系统来拒绝任何提交包含字符串“System.out”的代码的尝试。[evil grin])