Тестирование презентатора GWTP с асинхронными вызовами

Я использую GWTP, добавляя уровень контракта для абстрагирования знаний между Presenter и View, и я очень доволен результатом с GWTP. Я тестирую своих докладчиков с помощью Mockito.

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

Я обнаружил, что суть дела заключается в следующем: моим докладчикам часто требуется асинхронный вызов или вообще вызов метода объектов с обратным вызовом, чтобы продолжить мой поток докладчика (обычно они вложены).

Например :

  this.populationManager.populate(new PopulationCallback()
  {
     public void onPopulate()
     {
        doSomeStufWithTheView(populationManager.get());
     }
  });

В своих тестах я закончил проверку вызова населения() смоделированного объекта PopulationManager. Затем создать еще один тест для метода doSomeStufWithTheView().

Но я довольно быстро обнаружил, что это был плохой дизайн: любое изменение или рефакторинг заканчивались поломкой многих моих тестов и вынуждали меня создавать с самого начала другие, хотя функциональность презентера не менялась! Кроме того, я не проверял, действительно ли обратный вызов был тем, что я хотел.

Поэтому я попытался использовать метод mockito doAnswer, чтобы не нарушать процесс тестирования докладчика:

doAnswer(new Answer(){
     public Object answer(InvocationOnMock invocation) throws Throwable
     {
        Object[] args = invocation.getArguments();
        ((PopulationCallback)args[0]).onPopulate();
        return null;
     }
 }).when(this.populationManager).populate(any(PopulationCallback.class));

Я учел код, чтобы он был менее подробным (и внутренне менее зависимым от позиции arg):

doAnswer(new PopulationCallbackAnswer())
  .when(this.populationManager).populate(any(PopulationCallback.class));

Таким образом, издеваясь над менеджером населения, я все еще мог протестировать поток моего ведущего, в основном так:

@Test
public void testSomeStuffAppends()
{
  // Given
  doAnswer(new PopulationCallbackAnswer())
  .when(this.populationManager).populate(any(PopulationCallback.class));

  // When
  this.myPresenter.onReset();

  // Then
  verify(populationManager).populate(any(PopulationCallback.class)); // That was before
  verify(this.myView).displaySomething(); // Now I can do that.
}

Мне интересно, является ли это хорошим использованием метода doAnswer, или это запах кода, и можно использовать лучший дизайн?

Обычно мои докладчики, как правило, просто используют другие объекты (например, какой-либо шаблон посредника) и взаимодействуют с представлением. У меня есть презентатор с несколькими сотнями (~ 400) строк кода.

Опять же, является ли это доказательством плохого дизайна или для ведущего нормально быть многословным (потому что он использует другие объекты)?

Кто-нибудь слышал о каком-то проекте, который использует GWTP и чисто тестирует своего презентатора?

Надеюсь, объяснил доходчиво.

Заранее спасибо.

PS: я новичок в Stack Overflow, плюс мой английский все еще не хватает, если мой вопрос нуждается в чем-то, пожалуйста, скажите мне.


person Antonin M.    schedule 06.12.2012    source источник


Ответы (3)


Вы можете использовать ArgumentCaptor:
Посмотрите это запись в блоге, чтобы узнать подробности.

person Ümit    schedule 07.12.2012

Если я правильно понял, вы спрашиваете о дизайне/архитектуре.

Это не должно считаться ответом, это просто мои мысли.

Если я следовал коду:

    public void loadEmoticonPacks() {
    executor.execute(new Runnable() {
        public void run() {
            pack = loadFromServer();
            savePackForUsageAfter();
        }
    });
}

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

Если у меня есть что-то вроде:

accountManager.setListener(this);
....
public void onAccountEvent(AccountEvent event) {
....
}

Сначала я проверю, что мы подписались на события (и отписались на некоторые уничтожения), а также я бы проверил, что onAccountEvent выполняет ожидаемые сценарии.

UPD1. Вероятно, в примере 1 лучше было бы извлечь метод loadFromServerAndSave и проверить, что он не выполняется в потоке пользовательского интерфейса, а также проверить, что он делает все, как ожидалось.

UPD2. Для обработки событий лучше использовать фреймворк типа Guava Bus.

person Eugen Martynov    schedule 07.12.2012

Мы также используем этот шаблон doAnswer в наших тестах докладчиков, и обычно он отлично работает. Одно предостережение: если вы тестируете его таким образом, вы эффективно удаляете асинхронный характер вызова, то есть обратный вызов выполняется сразу после инициирования вызова сервера.

Это может привести к необнаруженным состояниям гонки. Чтобы проверить их, вы можете сделать это двухэтапным процессом: при вызове сервера метод ответа сохраняет только обратный вызов. Затем, когда это уместно в вашем тесте, вы вызываете что-то вроде flush() или onSuccess() в своем ответе (я бы предложил создать для этого служебный класс, который можно повторно использовать в других обстоятельствах), чтобы вы могли контролировать, когда обратный вызов для результата действительно вызывается.

person koljaTM    schedule 21.04.2013