Как сделать базовый тест-класс для Selenium тестов и выполнить инициализацию через JUnit RuleChain

    Этой статьей мы продолжаем серию публикаций о том, как мы автоматизировали в одном из крупных проектов ЛАНИТ процесс ручного тестирования (далее – автотесты) большой информационной системы (далее – Системы) и что у нас из этого вышло.

    Как эффективно организовать иерархию классов? Как распределить пакеты по проектному дереву? Как сделать так, чтобы забыть о мердж-конфликтах при команде в 10 человек? Эти вопросы всегда стоят при старте новой разработки и на них никогда не хватает времени.

    Источник

    В этой статье мы описываем структуру классов и организацию кода, которая позволила нам небольшими силами разработать более полутора тысяч end-2-end UI тестов на базе Junit и Selenium для крупной системы федерального значения. Более того, мы ее успешно поддерживаем и постоянно дорабатываем существующие сценарии.

    Здесь вы сможете найти практическое описание структуры иерархии базовых классов автотестов, разбиения проекта по функциональной модели java-packages и шаблоны-образцы реальных классов.

    Статья будет полезна всем разработчикам, которые разрабатывают автотесты на базе Selenium.

    Эта статья является частью общей публикации, в которой мы описывали, как небольшой командой выстраивали процесс автоматизации UI тестирования и разрабатывали для этого фреймворк на базе Junit и Selenium.

    Предыдущие части: 


    Реализация базового класса для всех тестов и JUnit RuleChain


    Концепция разработки автотестов, как было показано в предыдущей статье (Часть 2. Техническая. Архитектура и технический стек. Детали реализации и технические сюрпризы), базируется на идее фреймворка, при которой для всех автотестов предоставляется набор системных функций – они бесшовно интегрируются и дают возможность разработчикам автотестов концентрироваться на конкретных вопросах бизнес-реализации тест-классов.

    Фреймворк включает следующие функциональные блоки:

    • Rules – инициализация и финализация тестовых инфраструктурных компонентов как инициализация WebDriver и получение видеотеста. Более подробно описаны далее;
    • WebDriverHandlers – вспомогательные функции для работы с веб-драйвером как исполнение Java Script или доступ к логам браузера. Реализованы как набор статических state-less методов;
    • WebElements – библиотека типовых веб-элементов или их групп, которая содержит требуемую cross-function функциональность и типовое поведение. В нашем случае к такой функциональности относится возможная проверка завершения асинхронных операций на стороне веб-браузера. Реализованы как расширения веб-элементов из библиотек Selenium и Selenide.

    Инициализация тестового окружения. Rules


    Ключевым классом для всех тест-классов является BaseTest, от которого наследуются все тест-классы. BaseTest-класс определяет Junit «ранер» тестов и используемый RuleChain, как показано далее. Доступ из прикладных тестовых классов к функциям, предоставляемым rule-классами, осуществляется через статические методы rule-классов.   

    Образец кода BaseTest представлен на следующей врезке.

    @RunWith(FilterTestRunner.class)
    public class BaseTest {
        private TemporaryFolder downloadDirRule                                                    
                  = new TemporaryFolder(getSomething().getWorkingDir());
     
        @Rule
        public RuleChain rules = RuleChain
                .outerRule(new Timeout(TimeoutEnum.GLOBAL_TEST_TIMEOUT.value(),              
                                                             TimeUnit.SECONDS))
                .around(new TestLogger())
                .around(new StandStateChecker())
                .around(new WaitForAngularCreator())
                .around(downloadDirRule)
                .around(new DownloaderCreator(downloadDirRule))
                .around(new EnvironmentSaver())
                .around(new SessionVideoHandler())
                .around(new DriverCreator(downloadDirRule))
                .around(new BrowserLogCatcher(downloadDirRule))
                .around(new ScreenShooter())
                .around(new AttachmentFileSaver())
                .around(new FailClassifier())
                .around(new PendingRequestsCatcher());
     
       // набор методов провайдеров общесистемных данных из проперти файлов
       final protected SomeObject getSomething() {
            return Something.getData();
       }
       …
    }
    

    FilterTestRunner.class – расширение BlockJUnit4ClassRunner, обеспечивает фильтрацию состава исполняемых тестов на базе регулярных выражений по значению специальной аннотации Filter(value = «some_string_and_tag»). Реализация приведена далее.

    org.junit.rules.Timeout – используется для ограничения максимального продолжения тестов. Должна устанавливаться первой, так как запускает тест в новой ветке.

    TestLogger – класс, который позволяет тесту логировать события в формате json для использования в ELK-аналитике. Обогащает события данными теста из org.junit.runner.Description. Также дополнительно автоматически генерирует события для ELK в формате json для начала-завершения теста с его длительностью и результатом

    StandStateChecker – класс, который проверяет доступность веб-интерфейса целевого стенда ДО инициализации веб-драйвера. Обеспечивает быструю проверку, что стенд доступен в принципе.

    WaitForAngularCreator – класс, который инициализирует веб-драйвер хэндлер для контроля завершения асинхронных операций ангуляра. Используется для индивидуальной настройки «особых» тестов c длительными синхронными обращениями.

    org.junit.rules.TemporaryFolder – используется для задания уникальной временной папки для хранения файлов для операций загрузки и выгрузки файлов через веб-браузер.

    DownloaderCreator – класс, который обеспечивает поддержку операций выгрузки во временную директорию файлов, загруженных браузером и записанных через Sеlenoid видеофункцию.

    EnvironmentSaver – класс, который добавляет в Allure-отчет общую информацию о тестовом окружении.  

    SessionVideoHandler – класс, который выгружает файл видеотеста, при его наличии, и прикладывает к отчету Allure.

    DriverCreator – класс, который инициализирует WebDriver (самый главный класс для тестов) в зависимости от установленных параметров – локальный, solenoid или ggr-selenoid. Дополнительно класс исполняет набор обязательных для нашего тестирования Java Scripts. Все правила, которые обращаются к веб-драйверу, должны инициализироваться после этого класса.

    BrowserLogCatcher – класс, который считывает Severe сообщения из лога браузера, журналирует их для ELK (TestLogger) и прикладывает к Allure-отчету.

    ScreenShooter – класс, который для неуспешных тестов снимает скриншот экрана браузера и прикладывает его к отчету аллюр как WebDriverRunner.getWebDriver().getScreenshotAs(OutputType.BYTES)

    AttachmentFileSaver – класс, который позволяет приложить к Allure-отчету набор произвольных файлов, требуемых по бизнес-логике тестов. Используется для прикладывания файлов выгруженных или загружаемых в систему.

    FailClassifier – особый класс, который пытается в случае падения теста определить, было ли это падение вызвано инфраструктурными проблемами. Проверяет наличие на экране (после падения) особых модальных окон типа «Произошла системная ошибка №ХХХХХХХХХ», а также системных сообщений типа 404 и тому подобное. Позволяет разделить упавшие тесты на бизнес-падения (по сценарию) или системные проблемы. Работает через расширение org.junit.rules.TestWatcher.#failed метод.

    PendingRequestsCatcher – еще один особый класс, который пытается дополнительно классифицировать, было ли падение вызвано незавершенными, зависшими или очень длительными рест-сервисами между ангуляром и веб-фронтендом. Дополнительно к функциональному тестированию дает возможность определять проблемные и зависающие рест-сервисы при больших нагрузках, а также общую стабильность релиза. Для этого класс логирует в ELK все события с зависшими рест-запросами, которые он получает, запуская специальный js в браузере через открытый веб-драйвер.

    Шаблон реализации тест-класса 


    package autotest.test.<sub-system>;
    @Feature("Развернутое название подсистемы как в TMS")
    @Story("Развернутое название теста согласно TMS")
    @Owner("фамилия автоматизатора вносящего последние правки в тест кейс")
    @TmsLink("Номер теста согласно тест линку. Соответствует настроенному шаблону")
    public class <Сквозной номер тест кейса>_Test extends BaseTest {
        /**
        * Объявляем логин от которой проходит целевой тест
        **/
        Login orgTest;
     
        /** Объявляем все логины участвующие в тесте как вспомогательные **/
        Login loginStep1;
        ...
        Login loginStepN;
        
        /**
        * Здесь перечисляются бизнес-объекты которые требуются для проведения теста - Тестовая сцена
        * ... для всех требуемых бизнес объектов
        **/
          /**
        * Инициализация тестовой сцены
        * Для каждого бизнес объекта необходимо вывести в отчет инициализированное значение
        *       Utils.allure.message("Бизнес имя объекта в контексте тест-сценария", business_object)
        * Если класс инициализируется как null, то его выводят в отчет с указанием на каком шаге он будет заполнен.
        * Далее этот тип объекта должен быть дополнительно выведен в отчет в методах preconditions или actions
        *       Utils.allure.message("Номер созданного документа заполняемого на шаге Х", documentNumber)
         **/
     
        @Step("Инициализация тестовых объектов")
        private void init(Login login) {
            some_business_object = // создание требуемого объекта в или вне зависимости от login
            Utils.allure.message("Бизнес имя объекта в контексте тест-сценария", some_business_object)
                                // ... для всех требуемых бизнес объектов
     
            /** Получаем значения вспомогательных логинов */
            loginStep1 = LoginFactory.get(_Some_Login_);
            ...
            loginStepN = LoginFactory.get(_Some_Login_);
     
        }
        
        /**
        * Реализация конкретного теста
        **/
        @Test
        @Filter("Название теста для использования фильтрации на уровне JUnit")
        @DisplayName("Развернутое название теста согласно TMS")
        public void <Сквозной номер тест кейса>_<Полномочие>_<Уникальный_номер_проверки>_Test() {
           // Получаем значение тестовой организации
            orgTest = LoginFactory.get(_Some_Login_);  
            
            // Инициализируем тестовые данные в зависимости от логина
            init(orgTest);
        
            // Выполняем шаги тестовых сценариев-предусловий. Шаги предусловия не должны зависеть от значения логина
            preconditions();
            
            // Выполняем шаги целевого тестового сценария
            actions(orgTest);
        }
        
        /**
        * Выполнение требуемого набора активности для предусловий теста
        **/
        @Step("Предварительные условия")
        protected void preconditions() {
            loginStep1.login();
            new SomeAction().apply(someTestObject1, ..., someTestObjectN);
                Utils.allure.message("Получено значение для - Бизнес имя объекта в контексте тест-сценария", someTestObjectN)
            ...
        } 
        
        /**
        * Метод содержит декларативный перечень операций тестового сценария
        */
        @Step("Шаги теста")
        protected void actions(Login testLogin) {
            testLogin.reLogin();
            
            // Выполнение требуемой активности или набора активностей основного теста
            new SomeAction().apply(someTestObject1, ..., someTestObjectN);
        }
    }
    
     

    Шаблон реализации класса тест-сценария


    package autotest.business.actions.some_subsystem;
     
    public class SomeAction {
        
                    // ИНИЦИАЛИЗИРУЕМ СТРАНИЦЫ
        PageClassA pageA = new PageClassA();
        PageClassB pageB = new PageClassB();
     
        @Step("Полное наименование тестового сценария согласно TMS")
        @Description("Краткое описание тестового сценария согласно TMS")
        public void apply(someTestObject1, ..., someTestObjectN) {
            //Шаги сценария по TMS
            step_1(...);
            step_2(...);
            ...
            step_N(...);
        }
     
        @Step("Наименование шага 1 тестового сценария согласно TMS")
        private void step_1(...) {
           pageA.createSomething(someTestObject1);// just as an example create
        }
     
        @Step("Наименование шага 2 тестового сценария согласно TMS")
        private void step_2(...) {
        pageA.checkSomething(someTestObject1);// just as an example
        }
                    
                    ...
    }
    

    Реализация класса фильтрации тестов FilterTestRunner


    Здесь показана реализация расширения BlockJUnit4ClassRunner для фильтрации тестов на основании произвольных наборов тегов.

    /**
     * Custom runner for JUnit4 tests.
     * Provide capability to do additional filtering executed test methods
     * accordingly information that could be provided by {@link FrameworkMethod}
     */
     
    public class FilterTestRunner extends BlockJUnit4ClassRunner {
        private static final Logger LOGGER = Logger.getLogger(FilterTestRunner.class.getName());
        public FilterTestRunner(Class<?> klass) throws InitializationError {
            super(klass);
        }
        /**
        * This method should be used if you want to hide filtered tests completely from output
        **/
        @Override
        protected List<FrameworkMethod> getChildren() {
            //Получаем экземпляр фильтра, который сравнивает заданный фильтр со значением аннотации @Filter по требуемой логике
            TestFilter filter = TestFilterFactory.get();
            List<FrameworkMethod> allMethods = super.getChildren();
            List<FrameworkMethod> filteredMethod = new ArrayList<>();
            for (FrameworkMethod method: allMethods) {
                if (filter.test(method)) {
                    LOGGER.config("method [" + method.getName() +"] passed by filter [" + filter.toString() + "]"  );
                    filteredMethod.add(method);
                } else {
                    LOGGER.config("method [" + method.getName() +"] blocked by filter [" + filter.toString() + "]" );
                }
            }
            return Collections.unmodifiableList(filteredMethod);
        }
     
     
        /**
        * This method should be used if you want to skip filtered tests but no hide them
        @Override
        protected boolean isIgnored(FrameworkMethod method) {
            …
            if (filter.test(method)) {
                return super.isIgnored(method);
            } else {
               return true;
            }}
        */
    }
    

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

    Кстати, будем рады пополнить свою команду. Актуальные вакансии вот здесь.
    ГК ЛАНИТ
    Ведущая многопрофильная группа ИТ-компаний в РФ

    Комментарии 0

    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

    Самое читаемое