Автоматическое делегирование методов реализации

#java #unit-testing #proxy #mockito #delegation

#java #модульное тестирование #прокси #mockito #делегирование

Вопрос:

Могу ли я автоматически делегировать все вызовы методам другого экземпляра, которые используют тот же интерфейс?

У меня есть большой класс (я хочу протестировать), подобный этому:

 class MyClassUnderTest implements SomeInterface {
  public void anImportantMethod() {
  }
  @Override
  public void fromTheInterface() {
  }
  @Override
  public void fromTheInterface2() {
  }
  private void utilFunc() {}
}
  

Он реализует интерфейс

 interface SomeInterface {
  void fromTheInterface();
  void fromTheInterface2();
}
  

Учитывая это, во время unittest я хочу «скрыть» методы, унаследованные от интерфейса с различными реализациями. Для этого я написал служебный класс

 class DebugSomeInterface implements SomeInterface {
  @Override public void fromTheInterface() { log.debug("1"); }
  @Override public void fromTheInterface2() { log.debug("2"); };
}
  

Теперь мне нужно «делегировать» все возможные вызовы для этой реализации. Я делаю это вручную, производя из MyClassUnderTest моего собственного класса, делегируя все вызовы этому:

 class MyClassUnderTest_Mock extends MyClassUnderTest {
    DebugSomeInterface delegated = new DebugSomeInterface();

  @Override public void fromTheInterface() {
    delegated.fromTheInterface();
  }
  @Override public void fromTheInterface2() { 
    delegated.fromTheInterface2();
  };
  

Только у меня есть много реализующих классов SomeInterface , и выполнение этого вручную утомительно и подвержено ошибкам.

Я хотел бы (наполовину) автоматизированный способ создания MyClassUnderTest_Mock экземпляров, как mock(...) в Mockito. Может быть, что-то вроде

  MyClassUnderTest underTest = new MyClassUnderTest();
 DebugSomeInterface delegated = new DebugSomeInterface();
 MyClassUnderTest instance = mixin(underTest, SomeInterface.class, delegated);
  

Возможно, это привело бы к созданию «прокси», instance который делегирует все вызовы методов от SomeInterface к delegated , а остальные к underTest .

В ядре Java есть некоторый механизм с прокси-объектами, но я не могу собрать все это вместе.

Комментарии:

1. Вы могли бы шпионить (new MyClassUnderTest()), а затем имитировать методы, для которых вы хотите обеспечить поведение

Ответ №1:

Вы можете сделать что-то вроде этого:

 public static <T, U extends T> U createProxy(U classUnderTest, Class<T> interfaceType, T debugImplementation)
        throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
    U spy = Mockito.spy(classUnderTest);
    for (Method m : interfaceType.getMethods()) {
        Object[] params = new Object[m.getParameterTypes().length];
        for (int i = 0; i < params.length; i  ) {
            params[i] = Mockito.any();
        }
        Mockito.when(m.invoke(spy, params)).thenAnswer(new Delegate(debugImplementation, m));
    }
    return spy;
}

public static class Delegate implements Answer {
    private final Object delegate;

    private final Method delegateMethod;

    public Delegate(Object delegate, Method delegateMethod) {
        this.delegate = delegate;
        this.delegateMethod = delegateMethod;
    }

    @Override
    public Object answer(InvocationOnMock invocation) throws Throwable {
        return delegateMethod.invoke(delegate, invocation.getArguments());
    }
}
  

А затем используйте его как:

 MyClassUnderTest underTest = new MyClassUnderTest();
DebugSomeInterface delegated = new DebugSomeInterface();
MyClassUnderTest instance = createProxy(underTest, SomeInterface.class, delegated);
  

Это будет работать так, как вы хотите, но в качестве отказа от ответственности я думаю, что это плохая идея… Необходимость шпионить за тестируемым объектом и извлекать фрагменты тестируемого объекта должна быть красным флагом… Это плохая практика, вам нужно делать подобные вещи только тогда, когда сложность кода выше оптимальной и части вашего класса должны быть разделены на другие классы. Я никогда не сталкивался с подобной ситуацией, когда рефакторинг моей логики не был лучшим решением…