【问题标题】:Static factory methods and mocking静态工厂方法和模拟
【发布时间】:2015-12-01 16:26:00
【问题描述】:

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

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

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

假设您有一个名为 Option 的类,就像在 scala 中一样。如果您想为所有不存在的值重用相同的实例,则无法避免使用静态工厂方法。

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

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

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

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

谢谢。

【问题讨论】:

  • 你为什么需要mockOptionstatic 此类琐碎类的工厂方法很好。
  • 这里特意提到了 Scala 的Option 作为例子,这很有趣,因为Scala 没有static 成员的概念Option.apply() 是普通(实例)对象上的实例(非静态)方法。在 JVM 上,该实例对象存储在静态字段 Option.MODULE 中,因此调用 Option(1) 实际上会扩展为类似于 Option.MODULE.apply(1) 的内容。这与System.out.println() 非常相似,其中out 也是一个静态字段,但您可以通过System.setOut() 进行更改。换句话说,您没有必须对任何事情使用静态方法...

标签: java unit-testing testing tdd


【解决方案1】:

既然是理论问题,我就做一个理论回答。工厂范式是另一个理论的基础:注入。如果您创建的对象在需要时被注入,那么您只需注入您的模拟对象即可完成所有测试。有很多好书/网页可以帮助您入门。

【讨论】:

    【解决方案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 仅模拟一种特定方法而将其余方法保持原样。

    【讨论】:

      【解决方案3】:

      我的第一个选择是避免对任何东西进行模拟,因此是否有静态工厂方法没有区别。

      也就是说,如果我确实想要或需要模拟它们,那么我就去做。例如,假设您正在测试一个基于 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 实例。

      【讨论】:

      • 好的,据我所知,您是 JMockit 的开发人员之一,令人钦佩,因此对此表示敬意。但请理解其他人可能有不同的意见,请不要因此而被冒犯。我的观点是,当没有其他方法可以绕过它们时,mock 是一个很棒的实际折衷方案,但最好的设计代码是不需要 mock 的代码。 (虽然你写的第一句话似乎预料到了这一点。)
      【解决方案4】:

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

      如果 Java 从一开始就考虑到了 DI,那么 Integer.valueOf() 将是:

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

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

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

      你可以用静态工厂做任何事情,而勤奋地使用依赖注入则无法做到。

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

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

      (如果你这样做了,我什至要补充一点,你可以继续并配置你的版本控制系统,以拒绝任何提交包含字符串“System.out”的代码的尝试。[邪恶的笑容])子>

      【讨论】:

      • 如何注入 JSF FacesContext 或 Jasper Report 的 JasperRunManager,它们通常是从静态工厂方法获得的?
      • @Rogério 简单,在class 中创建一个依赖于JasperRunManager 的构造函数要求,并告诉您DI 框架从适当的static 工厂获取实例。这在 Spring 中非常常见,例如,您可以简单地注释一个方法,该方法返回对 static 工厂的调用 @Bean
      • @Rogério 我修改了我的答案以解决您的问题。
      • @BoristheSpider 当然,但这无法解释这样一个事实,即使用这些 API(特别是 FacesContext.getCurrentInstance())比创建没有商业价值的额外包装代码更简单、更容易。
      • @Rogério 根本不使用 DI 并在一个非常大的 main() 方法中编写所有内容更加简单和容易。它是通过痛苦的试验和错误发现的,但是比 更简单和更容易 现在 可悲的是很少与 更简单和更容易 以后。所以你的评论有点无关紧要。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-03-24
      • 1970-01-01
      相关资源
      最近更新 更多