【问题标题】:Testing a concrete third-party class with JMock使用 JMock 测试具体的第三方类
【发布时间】:2012-09-07 00:19:36
【问题描述】:

我有一个转发方法foo的课程:

void foo( Concrete c, String s ) { c.bar( s ); }

我想测试foo 实际上是否转发。对我来说不幸的是,Concrete 是第三方库中的一个类,是一个具体的类型,而不是一个接口。因此我必须在 JMock 中使用ClassImposteriser 来模拟Concrete,所以在我的测试用例中,我这样做:

@Test
public final void testFoo() {
   Mockery context = new JUnit4Mockery() {{
      setImposteriser(ClassImposteriser.INSTANCE);
   }};

  final Concrete c = context.mock(Concrete.class);
  final String s = "xxx" ;

  // expectations
  context.checking(new Expectations() {{

     oneOf (c).bar(s); // exception gets thrown from here
  }});


  new ClassUnderTest.foo( c, s );
  context.assertIsSatisfied();

}

不幸的是,Concrete.bar 又调用了一个抛出的方法。该方法是最终的,所以我不能覆盖它。此外,即使我注释掉 new ClassUnderTest.foo( c, s ); 行,当 JMock 设置异常时会抛出异常,而不是在调用 foo 时。

那么我该如何测试ClassUnderTest.foo 转发到Concrete.bar 的方法呢?

编辑:
是的,小节是最终的。

我的不是通用解决方案是使用第三方库中的“Tester”类来正确设置Concrete

【问题讨论】:

    标签: java unit-testing jmock


    【解决方案1】:

    从问题文本中不清楚 Concrete.bar() 是 final 还是 Concrete.somethingElse() 是 final 并从 Concrete.bar() 调用。

    如果 Concrete.bar() 不是最终的,请为 Concrete 创建一个手写存根,如下所示:

    public class ConcreteStub extends Concrete
    {
        public int numCallsToBar = 0;
        @Override
        public void bar(String s) { numCallsToBar++; }
    }
    

    在您的测试代码中:

    ConcreteStub c = new ConcreteStub();
    foo(c,"abc");
    assertEquals(1,c.numCallsToBar);
    

    如果 Concrete.bar() 是最终的,它会更复杂,答案取决于 Concrete 的复杂性和您的项目对 Concrete 类的使用。如果您对 Concrete 的使用足够简单,我会考虑将 Concrete 包装在一个界面 (Adapter Pattern) 中,然后您可以更轻松地模拟出来。

    适配器模式解决方案的好处:可能通过在项目使用 Concrete 之后命名接口来阐明行为。更容易测试。

    适配器模式解决方案的缺点:引入了更多类,可能对生产代码几乎没有好处。我不知道 Concrete 做了什么,将 Concrete 包装在界面中可能不切实际。

    【讨论】:

      【解决方案2】:

      请参阅http://www.jmock.org/mocking-classes.html 了解有关模拟类以及如何绕过最终限制的信息。

      【讨论】:

      • 你能扩展一下这个仅链接的答案吗?我没有标记,因为我看到您仍然是活跃用户,这是一个旧答案,但更多解释会有所帮助。
      【解决方案3】:

      如果一个方法是最终的,那么我们就无能为力了。如果这是一个第三方库,那么我们会考虑将其包装在一个 veneer 层并模拟 that,然后进行集成测试以针对该库进行测试。还有其他框架可以模拟锁定代码,但我们不支持它,因为我们认为这不是一个好主意。

      【讨论】:

        【解决方案4】:

        使用功能更强大的模拟工具,例如JMockit。你的测试可以写成:

        @Test
        public void testFoo(final Concrete c)
        {
          final String s = "xxx";
        
          new Expectations() {{
            c.bar(s);
          }};
        
          new ClassUnderTest().foo(c, s);
        }
        

        对于 JMockit,Concrete 是一个接口、一个最终类、一个抽象类或其他什么都没有区别。另外,不需要使用@RunWith,扩展一个基础测试类,或者调用像assertIsSatisfied()这样的任何方法;这一切都是以透明的方式自动完成的。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-13
          • 1970-01-01
          相关资源
          最近更新 更多