Статьи

Творческий способ использования реактивных расширений в общей настройке тестирования

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

Один из распространенных вариантов использования для настройки тестирования — запуск программы, например, внешней зависимости, такой как фиктивный сервер. В этом случае нам нужно дождаться успешного запуска программы. Напротив, тест должен прекратиться, как только запуск внешней программы завершится неудачно. Если в программе есть Java API, это легко. Однако это редко имеет место, и обычно используются более базовые API, такие как ProcessBuilderили Runtime.getRuntime().exec():

ProcessBuilder builder;

@BeforeMethod
protected void setUp() throws IOException {
    builder = new ProcessBuilder().command("script.sh");
    process = builder.start();
}

@AfterMethod
protected void tearDown() {
    process.destroy();
}

Традиционный способ справиться с этой проблемой — поставить большой Thread.sleep()сразу после запуска. Он не только зависел от системы, поскольку время запуска менялось от системы к системе, но и не решал проблему неудачного запуска. В этом более позднем случае драгоценное время вычислений, а также время ручного перезапуска были потеряны. Существуют лучшие решения, но они включают в себя много строк кода с некоторой (или высокой) степенью сложности. Разве не было бы неплохо, если бы у нас был простой и надежный способ запустить программу и, в зависимости от результатов, продолжить установку или не пройти тест? Rx на помощь!

Первый шаг — создать Observableпоток ввода запущенного процесса:

Observable<String> observable = Observable.create(subscriber -> {
    InputStream stream = process.getInputStream();
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) {
        String line;
        while ((line = reader.readLine()) != null) {
            subscriber.onNext(line);
        }
        subscriber.onCompleted();
    } catch (Exception e) {
        subscriber.onError(e);
    }
});

В приведенном фрагменте:

  • Каждый раз, когда процесс записывает на выходе, отправляется следующее событие
  • Когда больше нет выходных данных, отправляется полное событие
  • Наконец, если происходит исключение, это событие ошибки

По определению Rx любое из двух последних событий отмечает конец последовательности.

Как только наблюдаемое было создано, его просто нужно наблюдать за событиями. Простой скрипт, который генерирует одно событие, можно прослушать с помощью следующего фрагмента:

BlockingObservable<String> blocking = observable.toBlocking();
blocking.first();

Интересным моментом здесь является упаковка Observableэкземпляра в BlockingObservable. В то время как первый может быть объединен вместе, последний добавляет методы для управления событиями. На этом этапе first()метод будет прослушивать первое (и единственное) событие.

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

BlockingObservable<String> blocking = observable
    .filter("Done"::equals)
    .toBlocking();
blocking.first();

В этом случае, независимо от количества регулярных событий, filter()метод позволяет прослушивать только интересующее нас событие.

Однако предыдущие случаи не отражают реальность. В большинстве случаев сценарии установки должны запускаться до и выполняться параллельно с тестами, то есть конечное событие никогда не отправляется — по крайней мере, до завершения тестов. В этом случае возникают некоторые потоки. Rx позволил ему справиться с этим довольно легко:

BlockingObservable<String> blocking = observable
    .subscribeOn(Schedulers.newThread())
    .take(5)
    .toBlocking();
blocking.first();

Здесь есть простая разница: подписчик будет слушать новый поток благодаря subscribeOn()методу. В качестве альтернативы, события могли быть отправлены в другой поток с помощью observeOn()метода. Заметьте, я заменил filter()метод take()на вид, что заинтересован только в первых 5 событиях.

На этом настройка теста завершена. Удачного тестирования!