2014-03-06 46 views
5

假设我有以下类在单元测试的所有方法是否委托检查

public abstract class Foo{ 

    public int bar(){ 
    //implementation 
    } 
    public abstract int 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中覆盖和委托。

我正在尝试使用反射,它允许我调用每个方法,但我不知道如何检查方法是否实际委派。请参阅以下代码片段,了解我正在玩弄的想法:

@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。

+0

不错的描述和不错的想法:) –

+0

你能得到一些可以接受的答案吗?它有一个非常致命的缺陷,它并没有真正验证委托方法被调用(见下面的评论)。 – Krease

+0

@Krease我用接受的答案,它似乎工作。当包装程序未调用委托时,测试失败。只要在包装器和委托中使用相同的方法(总是在他们相互扩展的情况下看到的情况)将会通过测试。所以我不知道我100%理解你的评论 – Robin

回答

5

此代码似乎有伎俩。如果我向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()来检查这个。

+0

感谢您的警告,但我已经做了这个检查。只是让它不在问题中,以便它不会太长。 – Robin

+3

请不要使用'times(1)' - 这是Mockito中的默认验证模式,所以它在您的测试中变成了不必要的噪音。这行,可以写成'fooMethod.invoke(Mockito.verify(委托),参数);' –

+0

我没有意识到这一点,感谢大卫的信息! –

相关问题