Динамический Wait for condition на Java8

Давно хотела поделиться этим инструментом, и вот наконец дошли руки.

С чем сталкивается каждый автоматизатор в любом проекте, с которым ему приходится работать? Не знаю как вы, а я с тем, что любая операция на тестируемом окружении занимает какое-то время. При чем каждый раз это время разное.

Первое, чем страдают начинающие автоматизаторы — это Thread.sleep. Поищите у себя по проекту наличие этой строки? Если меньше 4 раз, мои позравления :slight_smile:
Каждый раз мы пишем какие-либо костыли на тему того, чтобы подождать, пока что-нибудь произойдет, и каждый раз приходим к тому что либо тест долго ждет(потому что кто-то ждет фиксированное время), либо в тесте напрямую вызывается Thread.sleep (и это выглядит ужасно), либо так или иначе мы пишем какой-нибудь do_while цикл.

Мне очень понравился подход в проекте Selenium WebDriver, где вы изначально задаете максимальное время ожидания события, и частоту опрашивания системы (я имею в виду WebDriverWait).

На основе него я и построила один вспомагательный унифицированный инструмент (на базе Java8, так что кто еще не перешел, еще один пункт в список “почему стоит это вделать сейчас”), который позволяет ожидать либо обязательное событие, либо событие, которое может произойти, но если не происходит, то в целом тоже не страшно.
Код доступен в репозитории GitHub, по свободе перенесу в базу знаний сюда.

В чем основной смысл. Есть некая система А, которая должна делать любую операцию не дольше 5 секунд, иначе пользователь будет недоволен и уйдет.
Мы знаем, что операция f1+f2 занимает в среднем 1 секунду, чаще всего в диапазоне 0,5 секунд до 1,5 секунд, но очень редко дольше. Операция же func(f1, f2) занимает около 2 секунд.

Тогда для ожидания результата первой операции мы можем создать ожидание сделующего вида: new Wait(5L, TimeUnit.SECONDS.toMilis(1)). А для второй new Wait(5L, TimeUnit.SECONDS.toMilis(2)). Это позволит оптимизировать время выполнения теста в некоторой степени, но чаще можно пожертвовать настолько точными определениями ради улучшения читаемости тестового кода и это упрощается до Wait imWait = new Wait(5L, TimeUnit.SECONDS.toMilis(2));

Теперь же есть два варианта развития событий:

  • Я жду что статус какой-то операции точно вернет мне строку «Ok». Пусть эта операция отображена функцией:

public String getStatus(@NotNull Integer statusCode) {
    return statusCode == 200 ? «Ok» : «Failure»;
}

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


imWait.until(()→getStatus(someCode).equals(«Ok»));

Но если мы не дождемся, то ошибка будет не слишком читаемая


TimeoutException: Timed out after 1 seconds waiting for BasicConditionsTest$$Lambda$1/1607460018@3d012ddd


Но вот в таком случае:


    @Test
    public void waitForStatusTest() {
        shortTime.until(status("Ok"));
    }
    public static Supplier<Boolean> status(final String status) {
        return new Supplier<Boolean>() {
            String current;
            @Override
            public String toString() {
                return String.format("status to be %s, current is %s", status, current);
            }
            @Override
            public Boolean get() {
                current = getStatus(someCode);
                return status.equals(current);
            }
        };
    }

TimeoutException: Timed out after 1 seconds waiting for status to be Ok, current is Failure


Мне кажется, что если тесты будут падать с такими сообщениями, то и репорты будут красивее, да и разобраться, что же произошло с тестом, будет проще

  • Я жду, что либо статус «Ok» и все абсолютно автоматически отработало и все хорошо, либо, если не «Ok», мне нужно сделать дополнительную операцию;

    @Test
    public void waitForStatusTest() {
        if(!shortTime.possible(status("Ok"))){
    		do something
    	}
    }

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

5 лайков

Я для себя не так давно обнаружил весьма удобную библиотеку awaitility, которая умеет ждать различные условия (включая lambda / hamcrest синтаксис), подключать слушателей, ловить / игнорить эксепшены и т.п. По аналогии с выше указанным примером, можно написать нечто следующее:

with()
 .conditionEvaluationListener(condition -> System.out.printf("%s should be %s, but found \"%s\"\n",
      condition.getAlias(), condition.getMatcher(), condition.getValue()))
 .await("Status")
 .atMost(5, TimeUnit.SECONDS)
 .pollInterval(1, TimeUnit.SECONDS)
 .until(() -> getStatus(500), equalTo("Ok"));

Либо вообще сократить до вменяемого минимума:

await()
 .atMost(5, TimeUnit.SECONDS)
 .pollInterval(1, TimeUnit.SECONDS)
 .until(() -> getStatus(500).equals("Ok"));
3 лайка

Да, неплохо, особенно интересно наличие Fibonacci Poll Interval

сарказм_on Вас джавистов видимо по жизни все или не любят, или обижают что даже пакеты Selenium какие то свои или это какой то не самый удачный способ продвинуть Java 8 в массы?сарказм_off
уже как 3 года юзаю:

creators = WebDriverWait(self.driver, 5).until(ec.element_to_be_clickable((By.CSS_SELECTOR, 'Element')))    

после скобок можно указывать тип екзепшена (класс/текст), и так же компейр строки, если в антил какая то ожидающая операция (ждать пока текст будет “вот таким”)

Только вот тема вообще не связана с WebDriver. :slight_smile:

Вот именно, что если нужно ожидать, пока в базе запись появится, или какой-то сервис отдаст энтити с определенным кондишеном. При чем здесь веб драйвер

если self.driver, заменить нужным объектом, а в антил положить задачу - получим просто ожидалку. А вообще да, не внимательно прочел) прошу вибачити(с)