Pull to refresh

Интеграционное тестирование web-приложения с Selenium WebDriver

Website development *
Интеграционное тестирование (в отличие от Unit- или модульного тестирования) это тестирование не отдельных атомарных компонентов системы (классов) а результата их взаимодействия между собой в какой-либо среде.

Волею судеб я занимаюсь разработкой своего рода интерфейсного фреймворка заточенного на определенные корпоративные нужды. Среда исполнения фреймворка — браузер, а по сему язык — JavaScript.

О том, как можно Unit-тестировать JavaScript я писал ранее, сейчас же расскажу о процессе интеграционного тестирования, применяемого в команде.

Selenium


С давних времен известен инструмент тестирования веб-приложений/страниц в браузере — Selenium. В плане его применения есть два основных пути, а именно:
  1. написание TestSuite в SeleniumIDE и прогон их через SeleniumTestRunner, или
  2. использование WebDriver

WebDriver это новая «фишка» Selenium, появившаяся во второй ветке продукта. Основная его суть — можно гонять тесты, описанные в коде (C#, Java, Python, Ruby), в разных браузерах и/или в виртуальной среде исполнения.

WebDriver


Selenium WebDriver это набор «биндингов» к разным языкам (C#, Java), позволяющий отдавать различные команды «подчиненному» браузеру.

Для каждого браузера имеется своя реализация WebDriver (FireFoxDriver, InternetExplorerDriver, ChromeDriver — сейчас включены в поставку, OperaSoftware разработали OperaDriver). Существует также «виртуальный» HtmlUnitDriver. В отличии от «браузерных» реализаций он не требует установленного браузера и за счет этого работает быстрее и платформонезависим, но есть и минусы — HtmlUnitDriver имеет «свою» реализацию JavaScript и потому поведение «богатых» веб-приложений может в нем отличаться. Для своих задач мы используем «браузерные» реализации, это позволяет проверить приложение именно в той среде, в которой оно будет исполняться впоследствии.

Кратко общая суть работы с WebDriver может быть описана так:
  • реализуется код, использующий какую-либо имплементацию WebDriver. Данный код выполняет какие-либо действия с веб-страницей и сравнивает результат с эталонным
  • WebDriver транслирует команды в запущенный браузер (при использовании «браузерной» реализации) и сообщает результаты «обратно в код»

Что умеет WebDriver


Ниже рассматривается «браузерная» реализация, суть расширение класса RemoteWebDriver (реализует интерфейс WebDriver).
  • поиск элементов: findElement(s)By*
    • CssSelector
    • ClassName
    • Id
    • LinkText
    • TagName
    • XPath
  • загрузка страницы, получение контента страницы
  • исполнение произвольного JavaScript
  • осуществление операций Drag-n-Drop

C «найденными» элементами (интерфейс WebElement)
  • получение текста (text)
  • получение значения (value)
  • click по элементу
  • ввод с клавиатуры (клавиша, сочетание клавиш, последовательность клавиш/сочетаний)

Среда исполнения тестов


В качестве языка для написания тестов была выбрана Java. Среда для исполнения — JUnit4.

DISCLAIMER: Не претендую на звание крутого джависта, посему если старшие коллеги найдут огрехи и всякие прочие «антипаттерны» — с удовольствием выслушаю в комментариях.

Базовый абстрактный класс веб-тестов.
@Ignore
abstract public class AbstractWebTest {

    protected static RemoteWebDriver _driver;
    // расположение тестовой страницы
    private String testPageLocation =
                String.format(
                        "http://%s:%s/test.html",
                        System.getProperty("test.httproot"),         // Web-сервер ...
                        System.getProperty("test.httpport", "80")   // и порт
                );
    // Используемая имплементация WebDriver
    private static String driverName =
                System.getProperty(
                        "test.driver",
                        "org.openqa.selenium.firefox.FirefoxDriver");

    /**
     * Перед каждым набором тестов - создаем инстанс драйвера.
     * Это автоматически запустит браузер
     */
    @BeforeClass
    public static void setUpDriver()
                throws ClassNotFoundException,
                          IllegalAccessException,
                          InstantiationException {
        _driver = (RemoteWebDriver) Class.forName(driverName).newInstance();
    }

    /**
     * Перед каждым тестом - открываем тестовую страницу
     */
    @Before
    public void setUp() {
        _driver.get(testPageLocation);
    }

    /**
     * После каждого набора тестов - закрываем инстанс дарйвера (закрываем браузер)
     */
    @AfterClass
    public static void tearDown() {
        _driver.close();
    }
}

Конкретный класс с набором тестов (для простоты убраны некоторые проверки, например на то, что элемент по CSS-селектору действительно найден и доступен на странице)
public class TestMoneyField extends AbstractWebTest {

    /**
     * При рендеринге поле ввода денежной суммы должно показать 0.00
     */
    @Test
    public void testRendering() {
        WebElement content =
           _driver.findElementByCssSelector("#FieldMoney .input-text-field");
        Assert.assertEquals("0.00", content.getValue());
    }

    /**
     * Проверим форматирование "триад"
     */
    @Test
    public void testInputWithoutDot() {

        WebElement content =
           _driver.findElementByCssSelector("#FieldMoney .input-text-field");
        content.sendKeys("999999");
        Assert.assertEquals("999 999.00", content.getValue());
    }
}

Все тесты запускаются с помощью отдельного таска Ant-билда:
<target name="integrationtest" depends="init, buildtests, deploytests">
   <junit haltonfailure="false">
      <sysproperty key="test.driver" value="org.openqa.selenium.firefox.FirefoxDriver" />
      <classpath>
         <pathelement location="${path.to.tests.jar}"/>
      </classpath>
      <batchtest>
         <fileset dir="${path.to.compiled.test.classes}">
            <include name="**/tests/Test*.class" />
         </fileset>
      </batchtest>
   </junit>

   <junit haltonfailure="false">
      <sysproperty key="test.driver" value="org.openqa.selenium.ie.InternetExplorerDriver" />
      <classpath>
         <pathelement location="${path.to.tests.jar}"/>
      </classpath>
      <batchtest>
         <fileset dir="${path.to.compiled.test.classes}">
            <include name="**/tests/Test*.class" />
         </fileset>
      </batchtest>
   </junit>
</target>

Данный таск прогонит все известные тесты из классов, чьи имена начинаются с Test под браузерами Firefox и InternetExplorer. В зависимостях таски с базовой инициализацией, компиляцией и выгрузкой скомпилированных тестов на тестовую площадку.

Фишки-плюшки


Некоторые «браузерные» реализации (Firefox, Opera, Chrome) поддерживают снятие скриншотов. Это может быть полезно дабы зафиксировать визуальное состояние, в котором пребывала тестовая страница в момент, когда тест не прошел. Для этого подойдет функционал JUnit4 — TestWatchman.
@Ignore
abstract public class AbstractWebTest {

    // Папка для скриншотов
    private String screenshotDir =
            System.getProperty("test.screenshotDir", "");

    @Rule
    public MethodRule watchman = new TestWatchman() {

        /**
         * Будет вызван при каждом "проваленном" тесте
         * @param e Брошенное тестом исключение
         * @param method Тест-метод
         */
        @Override
        public void failed(Throwable e, FrameworkMethod method) {
            if(_driver instanceof TakesScreenshot && !screenshotDir.equals("")) {
                String browserName = _driver.getClass().getName();
                String testSuiteName = method.getMethod().getDeclaringClass().getName();
                browserName =
                        browserName.substring(browserName.lastIndexOf('.') + 1);
                testSuiteName =
                        testSuiteName.substring(testSuiteName.lastIndexOf('.') + 1);

                byte[] screenshot =
                        ((TakesScreenshot)_driver).getScreenshotAs(OutputType.BYTES);
                try {
                    FileOutputStream stream =
                            new FileOutputStream(
                                    new File(
                                        String.format("%s/screenshot_%s_%s_%s.png",
                                                screenshotDir,
                                                browserName,
                                                testSuiteName,
                                                method.getName())));
                    stream.write(screenshot);
                    stream.close();
                } catch (IOException e1) {
                    e1.printStackTrace(System.out);
                }
            }
        }
    };
    // все остальное...

}

Добавим переменную с путем к папке со скриншотами в Ant-билд
   <junit haltonfailure="false">
      <sysproperty key="test.driver" value="org.openqa.selenium.firefox.FirefoxDriver" />
      <sysproperty key="test.screenshotDir" value="${screenshotsDir}" />
      <classpath>
         <pathelement location="${path.to.tests.jar}"/>
      </classpath>
      <batchtest>
         <fileset dir="${path.to.compiled.test.classes}">
            <include name="**/tests/Test*.class" />
         </fileset>
      </batchtest>
   </junit>

Интеграция


В текущей реализации Ant-билд гоняется через Jetbrains TeamCity. Запуск билда настроен на сброс кода в SVN. Интеграционные тесты — часть общей процедуры тестирования. При провале любого из интеграционных тестов снимается скриншот и публикуется как «артефакт» билда — можно видеть не только какие тесты «отъехали» после сброса в транк какого-либо функционала, но и увидеть «как» они «отъехали».

В настоящее время используется тестирование под IE и Firefox, Chrome не подключен по причине некоторых трудностей с интеграцией (судя по всему в ChromeDriver присутствуют некоторые ошибки, не позволяющие нормально искать элементы на странице в некоторых случаях — по состоянию на 2.0b1, сейчас доступна 2.0b2 но работу с ней пока не пробовали)
Tags:
Hubs:
Total votes 42: ↑42 and ↓0 +42
Views 33K
Comments Comments 22