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

Как эффективно организовать иерархию классов? Как распределить пакеты по проектному дереву? Как сделать так, чтобы забыть о мердж-конфликтах при команде в 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)