Тестирование вызова асинхронного метода - PullRequest
5 голосов
/ 29 сентября 2011

Ниже приведена упрощенная настройка моего приложения. У него есть класс Foobar, который вызывает метод фасада для извлечения данных. Затем фасад вызывает веб-сервис для фактического получения данных, затем немного манипулирует данными и возвращает их в Foobar.

Теперь, поскольку запуск веб-службы может занять много времени, вызов метода для фасада должен быть асинхронным. Следовательно, метод фасада не имеет возвращаемого значения, но вместо этого метод использует объект обратного вызова. Посмотрите на пример и продолжайте читать ниже.

public class Foobar {
    private List<DTO> dtos;

    @Autowired
    private Facade facade;

    public void refresh() {
        facade.refreshFoobar(new CallBack() {
            public void dataFetched(List<DTO> dtos) {
                setDtos(dtos);
            }

        });
    }    

    public void setDtos(List<DTO> dtos) {
        this.dtos = dtos;
    }
}


public class Facade {

    ...

    public void refreshFoorbar(CallBack cb) {
        // Fetch data from a web service
        List<DTO> dtos = webService.getData();  
        // Manipulate DTOs
        ....
        // call on the callback method
        cb.dataFecthed(dtos);
    }

}

У меня есть два способа сделать метод фасада асинхронным: создать поток вручную или с помощью аннотации springs @Async.

public class Facade {

    public void refreshFoorbar(CallBack cb) {
        new Thread() {

            @Override
            public void run() {
                ....
            }

        }.start();

    }
}

// ... OR ...

public class Facade {

    @Async
    public void refreshFoorbar(CallBack cb) {
        ....    
    }
}

Моя проблема в том, что теперь мне нужно написать интеграционный тест для этой цепочки вызовов методов. Я думаю, что нужно заставить синхронный вызов асинхронного фасада быть синхронным при запуске интеграционного теста, иначе я не буду точно знать, когда смогу сделать соответствующие утверждения. Единственная идея сделать синхронный вызов метода состоит в том, чтобы использовать потоки, обработанные вручную, и сделать поток условным (так, для целей тестирования у меня есть предложение if, которое определяет, следует ли запускать метод фасада в отдельном потоке или нет).

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

Это где мне нужны ваши предложения, как бы вы решили мою проблему? Обратите внимание, я использую junit для модульных и интеграционных тестов.

Ответы [ 2 ]

12 голосов
/ 29 сентября 2011

Простым решением было бы вернуть объект Future, подобный этому,

@Async
public Future<String> refreshFoorbar(CallBack cb) {
    yourHeavyLifting(); //asynchronous call
    return new AsyncResult<String>("yourJobNameMaybe");   
}

И в своем тесте возьмите будущую ссылку и вызовите метод get () .

future.get(); // if its not already complete, waits for it to complete
assertTrue(yourTestCondition)

Это сообщение в блоге показывает образец.

6 голосов
/ 29 сентября 2011

Когда JUnit тестирует подобные вещи, я использую тестовый обратный вызов с CountDownLatch, который отсчитывается обратным вызовом и await() редактируется тестовым методом.

private static class TestingCallback implements Callback {
    private final CountDownLatch latch;
    public TestingCallback(CountDownLatch latch) {
        this.latch = latch;
    }
    @Override public void onEvent() {
        this.latch.countDown();
    }
}

@Test
public void testCallback() {
    final CountDownLatch latch = new CountDownLatch(1);

    classUnderTest.execute( new TestCallback(latch) );

    assertTrue(latch.await(30, TimeUnit.SECONDS));
}

Если обратный вызов вызывается (асинхронно) тестируемым кодом, защелка возвращает true, и тест проходит.Если обратный вызов не вызывается, тест завершается через тридцать секунд, и утверждение не выполняется.

...