【问题标题】:Verify that all getter methods are called验证是否调用了所有 getter 方法
【发布时间】:2016-01-13 17:44:41
【问题描述】:

我有以下测试,我需要验证是否调用了 Person 类的所有 getter。到目前为止,我已经使用了 mockito 的 verify() 来确保调用每个 getter。有没有办法通过反思来做到这一点?可能会在 Person 类中添加一个新的 getter,但测试会错过它。

public class GetterTest {
    class Person{

        private String firstname;
        private String lastname;

        public String getFirstname() {
            return firstname;
        }

        public String getLastname() {
            return lastname;
        }
    }

    @Test
    public void testAllGettersCalled() throws IntrospectionException{
        Person personMock = mock(Person.class);
        personMock.getFirstname();
        personMock.getLastname();

        for(PropertyDescriptor property : Introspector.getBeanInfo(Person.class).getPropertyDescriptors()) {
            verify(personMock, atLeast(1)).getFirstname();
            //**How to verify against any getter method and not just getFirstName()???**
        }
    }
}

【问题讨论】:

    标签: java unit-testing reflection junit mockito


    【解决方案1】:

    一般来说,不要模拟被测类。如果您的测试是针对 Person 的,那么您永远不会在其中看到 Mockito.mock(Person.class),因为这是一个非常明显的迹象,表明您正在测试模拟框架而不是被测系统。

    相反,您可能想要创建一个spy(new Person()),它将使用真正的构造函数创建一个真正的 Person 实现,然后将其数据复制到 Mockito 生成的代理。您可以使用MockingDetails.getInvocations() 反射性地检查是否调用了每个 getter。

    // This code is untested, but should get the point across. Edits welcome.
    // 2016-01-20: Integrated feedback from Georgios Stathis. Thanks Georgios!
    
    @Test
    public void callAllGetters() throws Exception {
      Person personSpy = spy(new Person());
      personSpy.getFirstname();
      personSpy.getLastname();
    
      assertAllGettersCalled(personSpy, Person.class);
    }
    
    private static void assertAllGettersCalled(Object spy, Class<?> clazz) {
      BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
      Set<Method> setOfDescriptors = beanInfo.getPropertyDescriptors()
          .stream()
          .map(PropertyDescriptor::getReadMethod)
          .filter(p -> !p.getName().contains("getClass"))
          .collect(Collectors.toSet());
      MockingDetails details = Mockito.mockingDetails(spy);
      Set<Method> setOfTestedMethods = details.getInvocations()
          .stream()
          .map(InvocationOnMock::getMethod)
          .collect(Collectors.toSet());
      setOfDescriptors.removeAll(setOfTestedMethods);
      // The only remaining descriptors are untested.
      assertThat(setOfDescriptors).isEmpty();
    }
    

    可能有一种方法可以在 Mockito 生成的间谍上调用 verifyinvoke,但这似乎非常脆弱,并且非常依赖于 Mockito 内部结构。

    顺便说一句,测试 bean 样式的 getter 似乎是对时间/精力的一种奇怪使用。一般来说,重点是测试可能会更改或中断的实现。

    【讨论】:

    • 太棒了!关于你最后的建议,我认为测试不是针对 Person 而是针对诸如 PersonBuilder、PersonCloner 之类的类
    • @Raffaele:是的,但希望更好的解决方案是实际检查每个字段是否被复制,这意味着(但不要求)每个 getter 都被调用。
    • 这实际上是在检查所有 getter 方法的调用时花费最少的精力。但有些变化:BeanInfo beanInfo = Introspector.getBeanInfo(Person.class); 并为 setOfDescriptors 添加了一个过滤器:.filter(p -&gt; !p.contains("getClass")),因此不考虑此方法。
    • @GeorgiosStathis 感谢您的反馈!我已经更新了答案。
    【解决方案2】:

    我可以为您的问题想出两种解决方案:

    1. 以编程方式生成 Builder 代码,因此您无需运行测试。 Java 代码由程序生成,从不被用户编辑。而是测试生成器。使用文本模板并从序列化的域模型或直接从 Java 编译的类构建定义(您需要一个依赖于 bean 的单独模块)

    2. 针对代理库编写测试。问题是常规代理只能实现接口,不能实现常规类,而且为Javabeans有接口非常麻烦。如果你选择这条路线,我会选择Javassist。我编写了一个可运行的示例并将其放入on GitHub。测试用例使用代理工厂来实例化 bean(而不是使用 new

    public class CountingCallsProxyFactory {
    
        public <T> T proxy(Class<T> classToProxy) {
            ProxyFactory factory = new ProxyFactory();
            factory.setSuperclass(classToProxy);
            Class clazz = factory.createClass();
            T instance =  (T) clazz.newInstance();
            ProxyObject proxy = (ProxyObject) instance;
            MethodCallCounter handler = new MethodCallCounter();
            proxy.setHandler(handler);
            return instance;
        }
    
        public void verifyAllGettersCalled(Object bean) {
            // Query the counter against the properties in the bean
        }
    }
    

    计数器保存在类MethodCallCounter

    【讨论】:

    • 生成 Person 构建器然后测试生成器似乎是一种解决方案。带有代理对象的第二个看起来有点复杂,但我想试一试Javassist。感谢您的示例代码!
    • 我赞成生成器方法,但同样,您要么使用 Java 以外的其他语言来定义 bean 和构建器,要么您将拥有至少三个编译模块:bean、构建器和应用程序
    猜你喜欢
    • 1970-01-01
    • 2016-06-12
    • 1970-01-01
    • 1970-01-01
    • 2014-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-26
    相关资源
    最近更新 更多