Если вы тестируете веб-приложение, вы не ошибетесь с Selenium WebDriver . Но в этом мире совершенства ajax-y в Web 2.0 может возникнуть проблема с асинхронной природой современных сайтов. Когда все, что у нас было — это веб 1.0, вы нажимали кнопку, и в итоге вы получали новую страницу, или, если вам не повезло: сообщение об ошибке. Но теперь, когда вы нажимаете ссылки, происходят всякие забавные вещи — некоторые из которых происходят быстрее, чем другие. С точки зрения пользователя это создает отличный пользовательский интерфейс. Но если вы пытаетесь автоматизировать тестирование, вы можете получить всевозможные ужасные условия гонки .
Thread.sleep
Наивный подход — писать свои тесты так же, как вы делали это раньше: вы нажимаете кнопки и утверждаете, что то, что вы ожидали, действительно произошло. По большей части это работает. Сайты, как правило, достаточно быстрые, даже в среде непрерывной интеграции, и к тому моменту, когда тестовый жгут ищет изменения, это уже произошло.
Но затем … все немного замедляется, и вы начинаете мерцать — тесты, которые иногда проходят, а иногда не проходят. Таким образом, вы добавляете небольшую задержку. Это займет всего 500 миллисекунд, пока вы ждете, пока сервер ответит и обновит страницу. Затем через месяц он снова мигает, так что вы делаете это 1 секунду. Потом два … потом двадцать.
Проблема в том, что каждый тест выполняется в темпе, в котором он работает медленнее всего . Если вход в систему обычно занимает 0,1 секунды, но иногда занимает 10 секунд, когда среда перегружена — тест должен ждать 10 секунд, чтобы не мерцать. Это означает, что даже если приложение часто работает быстрее, тест должен ждать на всякий случай .
Прежде чем вы это узнаете, ваши тесты просматриваются и занимают несколько часов — вы потеряли цикл быстрой обратной связи, и разработчики больше не доверяют тестам.
Пример
К счастью, у WebDriver есть решение этой проблемы. Это позволяет вам подождать, пока какое-то условие пройдет, поэтому вы можете использовать его для контроля скорости ваших тестов. Чтобы продемонстрировать это, я создал простое веб-приложение с формой входа в систему — источник доступен на github . Вход в систему занимает глупое время, поэтому тесты должны реагировать на это, чтобы не вводить произвольные ожидания.
Приложение очень простое — поле имени пользователя и пароля с кнопкой аутентификации, которая делает запрос ajax для входа пользователя в систему. Если вход выполнен успешно, мы обновляем экран, чтобы сообщить пользователю об этом.
Первым делом нужно написать наш тест (очевидно, в реальном мире мы написали бы тест до нашего производственного кода, но здесь интересен тест, а не то, что мы тестируем — поэтому мы сделаем это в неправильном порядке только один раз)
@Test public void authenticatesUser() { driver.get("http://localhost:8080/"); LoginPage loginPage = LoginPage.open(driver); loginPage.setUsername("admin"); loginPage.setPassword("password"); loginPage.clickAuthenticate(); Assert.assertEquals("Logged in as admin", loginPage.welcomeMessage()); }
У нас есть объект страницы, который инкапсулирует функциональность входа в систему. Мы предоставляем имя пользователя и пароль, затем нажимаем кнопку аутентификации. Наконец мы проверяем, что страница обновлена с пользовательским сообщением. Но как мы справились с асинхронной природой этого приложения?
WebDriverWait
С помощью волшебства WebDriverWait мы можем подождать, пока функция вернет true, прежде чем мы продолжим:
public void clickAuthenticate() { this.authenticateButton.click(); new WebDriverWait(driver, 30).until(accountPanelIsVisible()); } private Predicate<WebDriver> accountPanelIsVisible() { return new Predicate<WebDriver>() { @Override public boolean apply(WebDriver driver) { return isAccountPanelVisible(); } }; } private boolean isAccountPanelVisible() { return accountPanel.isDisplayed(); }
Наш метод clickAuthenticate нажимает кнопку, а затем инструктирует WebDriver дождаться выполнения нашего условия. Условие определяется с помощью предиката (давай Java, где замыкания?). Предикат — это просто метод, который будет запускаться, чтобы определить, является ли условие истинным или нет. В этом случае мы делегируем метод isAccountPanelVisible объекта страницы. Он делает именно то, что говорит на банке, он использует элемент страницы, чтобы проверить, виден ли он еще. Просто нет?
Таким образом, мы можем определить условие, которое мы хотим быть верным, прежде чем мы продолжим. В этом случае условие выхода метода clickAuthenticate состоит в том, что процесс асинхронной аутентификации завершен. Это означает, что тестам не нужно беспокоиться о внутренней механике страницы — о том, является ли операция асинхронной или нет. Тест просто указывает, что тестировать, объект страницы инкапсулирует, как это сделать .
Javascript
Это все хорошо, ожидая, когда элементы будут видны или определенный текст будет представлен, но иногда нам может потребоваться более тонкий контроль. Хороший подход заключается в обновлении состояния Javascript после завершения действия. Это означает, что тесты могут проверять переменные javascript, чтобы определить, завершено ли что-то или нет — что позволяет очень четко и просто согласовать производственный код и тест.
Продолжая наш пример входа в систему, вместо того, чтобы полагаться на то, что <div> становится видимым, мы могли бы вместо этого установить переменную Javascript. Код фактически делает и то и другое , поэтому мы можем провести два теста. Второй выглядит следующим образом:
public void authenticate() { this.authenticateButton.click(); new WebDriverWait(driver, 30).until(authenticated()); } private Predicate<WebDriver> authenticated() { return new Predicate<WebDriver>() { @Override public boolean apply(WebDriver driver) { return isAuthenticated(); } }; } private boolean isAuthenticated() { return (Boolean) executor().executeScript("return authenticated;"); } private JavascriptExecutor executor() { return (JavascriptExecutor) driver; }
Этот пример следует той же базовой схеме, что и предыдущий тест, но мы используем другой предикат. Вместо того, чтобы проверять, является ли элемент видимым или нет, мы вместо этого получаем статус переменной Javascript. Мы можем сделать это, потому что каждый WebDriver также реализует JavascriptExecutor, позволяющий нам запускать Javascript внутри браузера в контексте теста. Т.е. скрипт «return authenticated» выполняется в браузере, но результат возвращается в наш тест. Мы просто проверяем состояние переменной, которая изначально имеет значение false, и после завершения процесса аутентификации устанавливается значение true.
Это позволяет нам тесно координировать наш производственный и тестовый код без риска мерцания тестов из-за условий гонки.
От http://blog.activelylazy.co.uk/2012/01/29/testing-asynchronous-applications-with-webdriverwait/