【问题标题】:Checking in a unit test whether all methods are delegated检查单元测试是否所有方法都被委托
【发布时间】:2014-04-09 03:48:09
【问题描述】:

假设我有以下课程

public abstract class Foo{

  public int bar(){
    //implementation
  }
  public abstract int bar2();
}

和一个基类,以便更轻松地为此类编写装饰器

public class FooWrapper{
  private final Foo delegate;

  protected FooWrapper( Foo delegate ){
    this.delegate = delegate;
  }
  @Override
  public int bar(){
    return delegate.bar()
  }
  @Override
  public int bar2(){
    return delegate.bar2();
  }
}

FooWrapper 类允许您为 Foo 编写装饰器,您只需覆盖您需要的方法。

现在我想为FooWrapper 编写一个测试,检查所有方法是否默认委托。当然我可以写类似的东西

@Test
public void barShouldBeDelegated(){
  Foo delegate = Mockito.mock( Foo.class );
  FooWrapper wrapper = new FooWrapper( delegate );
  wrapper.bar();
  Mockito.verify( delegate ).bar();
}

但这需要我在每次将方法添加到Foo 时添加一个新的测试方法。我希望每次向Foo 添加一个方法时都会失败的测试,我忘记在FooWrapper 中覆盖和委托。

我试图使用允许我调用每个方法的反射,但我不知道如何检查该方法是否实际被委托。有关我正在玩弄的想法,请参阅以下 sn-p:

  @Test
  public void testAllMethodsAreDelegated() throws Exception{
    Foo delegate = mock(Foo.class);
    FooWrapper wrapper = new FooWrapper(delegate);

    Class<?> clazz = wrapper.getClass();
    Method[] methods = clazz.getDeclaredMethods();

    for (Method method : methods) {
      Class<?>[] parameterTypes = method.getParameterTypes();
      Object[] arguments = new Object[parameterTypes.length];
      for (int j = 0; j < arguments.length; j++) {
        arguments[j] = Mockito.mock(parameterTypes[j]);
      }
      method.invoke(wrapper, arguments);

      // ?? how to verify whether the delegate is called
      // Mockito.verify( delegate ).??? does not work 
      // as I cannot specify the method by name
      }
    }
  }

任何想法是否可以编写这样的测试。请注意,我可以使用的唯一模拟框架是 Mockito。

【问题讨论】:

  • 很好的描述和好主意:)
  • 你能从接受的答案中得到一些有用的东西吗?它有一个非常致命的缺陷,它实际上并没有验证委托方法是否被调用(见下面的评论)。
  • @Krease 我使用了接受的答案,它似乎有效。当包装器不调用委托时测试失败。仅在包装器和委托中使用相同的方法(在它们相互扩展时总是如此)将使测试失败。所以我不确定我 100% 理解你的话
  • 奇怪 - 我尝试了同样的事情并且能够以多种方式打破它.. (另外,从这里的例子FooWrapper 不扩展Foo,但包含一个 - 它们可以是不同的类层次结构)。我会进一步调查我的具体案例,也许还有其他原因导致我的测试无法正确......
  • ...我错了,它确实有效——我对我的版本进行了一些调整,否定了它有效的原因。我会更新答案以澄清。

标签: java unit-testing decorator mockito


【解决方案1】:

这段代码似乎可以解决问题。如果我向 Foo 添加了一个方法并且没有将它包含在 FooWrapper 中,那么测试将失败。

    FooWrapper wrapper = new FooWrapper(delegate);
    Foo delegate = Mockito.mock(Foo.class);

    // For each method in the Foo class...
    for (Method fooMethod : Foo.class.getDeclaredMethods()) {
        boolean methodCalled = false;

        // Find matching method in wrapper class and call it
        for (Method wrapperMethod : FooWrapper.class.getDeclaredMethods()) {
            if (fooMethod.getName().equals(wrapperMethod.getName())) {

                // Get parameters for method
                Class<?>[] parameterTypes = wrapperMethod.getParameterTypes();
                Object[] arguments = new Object[parameterTypes.length];
                for (int j = 0; j < arguments.length; j++) {
                    arguments[j] = Mockito.mock(parameterTypes[j]);
                }

                // Invoke wrapper method
                wrapperMethod.invoke(wrapper, arguments);

                // Ensure method was called on delegate exactly once with the correct arguments
                fooMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments);

                // Set flag to indicate that this foo method is wrapped properly.
                methodCalled = true;
            }
        }

        assertTrue("Foo method '" + fooMethod.getName() + "' has not been wrapped correctly in Foo wrapper", methodCalled);
    }

您的代码中缺少的关键行是

fooMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments);

这可能看起来有点奇怪,但这是可行的,因为它以 Mockito 期望的相同顺序调用事物:首先调用 Mockito.verify(delegate)(它在内部启动 Mockito 验证),然后方法是调用。类似的非反射调用看起来像Mockito.verify(delegate).foo()。使用这个“为什么”来帮助使代码适应不同的用例,而不会破坏测试的验证方式。

请注意,我会在每个循环的开头添加一个检查,以遍历 getDeclaredMethods() 的结果。此方法返回所有方法,无论它们是公共的、私有的、受保护的等。尝试访问不可访问的方法会引发异常。您可以使用 Method.isAccessible() 来检查这一点。

【讨论】:

  • 感谢您的警告,但我已经进行了这项检查。只是把它排除在外,让它不会太长。
  • 请不要使用times(1) - 这是Mockito中默认的验证模式,所以它只会在你的测试中变成不必要的噪音。那行,可以写成fooMethod.invoke(Mockito.verify(delegate), arguments);
猜你喜欢
  • 1970-01-01
  • 2013-08-24
  • 1970-01-01
  • 1970-01-01
  • 2012-05-10
  • 1970-01-01
  • 2010-12-22
  • 2014-07-21
  • 1970-01-01
相关资源
最近更新 更多