Этой статьей мы продолжаем серию публикаций о том, как мы автоматизировали в одном из крупных проектов ЛАНИТ процесс ручного тестирования (далее – автотесты) большой информационной системы (далее – Системы) и что у нас из этого вышло.
Как эффективно организовать иерархию классов? Как распределить пакеты по проектному дереву? Как сделать так, чтобы забыть о мердж-конфликтах при команде в 10 человек? Эти вопросы всегда стоят при старте новой разработки и на них никогда не хватает времени.
Источник
В этой статье мы описываем структуру классов и организацию кода, которая позволила нам небольшими силами разработать более полутора тысяч end-2-end UI тестов на базе Junit и Selenium для крупной системы федерального значения. Более того, мы ее успешно поддерживаем и постоянно дорабатываем существующие сценарии.
Здесь вы сможете найти практическое описание структуры иерархии базовых классов автотестов, разбиения проекта по функциональной модели java-packages и шаблоны-образцы реальных классов.
Статья будет полезна всем разработчикам, которые разрабатывают автотесты на базе Selenium.
Эта статья является частью общей публикации, в которой мы описывали, как небольшой командой выстраивали процесс автоматизации UI тестирования и разрабатывали для этого фреймворк на базе Junit и Selenium.
Предыдущие части:
Концепция разработки автотестов, как было показано в предыдущей статье (Часть 2. Техническая. Архитектура и технический стек. Детали реализации и технические сюрпризы), базируется на идее фреймворка, при которой для всех автотестов предоставляется набор системных функций – они бесшовно интегрируются и дают возможность разработчикам автотестов концентрироваться на конкретных вопросах бизнес-реализации тест-классов.
Фреймворк включает следующие функциональные блоки:
Ключевым классом для всех тест-классов является BaseTest, от которого наследуются все тест-классы. BaseTest-класс определяет Junit «ранер» тестов и используемый RuleChain, как показано далее. Доступ из прикладных тестовых классов к функциям, предоставляемым rule-классами, осуществляется через статические методы rule-классов.
Образец кода BaseTest представлен на следующей врезке.
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 в браузере через открытый веб-драйвер.
Здесь показана реализация расширения BlockJUnit4ClassRunner для фильтрации тестов на основании произвольных наборов тегов.
В следующей части я расскажу о том, как мы реализовали процесс выгрузки файла из контейнера с браузером в тестовый фреймворк, и решили вопрос с поиском имени загруженного браузером файла.
Кстати, будем рады пополнить свою команду. Актуальные вакансии вот здесь.
Как эффективно организовать иерархию классов? Как распределить пакеты по проектному дереву? Как сделать так, чтобы забыть о мердж-конфликтах при команде в 10 человек? Эти вопросы всегда стоят при старте новой разработки и на них никогда не хватает времени.
Источник
В этой статье мы описываем структуру классов и организацию кода, которая позволила нам небольшими силами разработать более полутора тысяч end-2-end UI тестов на базе Junit и Selenium для крупной системы федерального значения. Более того, мы ее успешно поддерживаем и постоянно дорабатываем существующие сценарии.
Здесь вы сможете найти практическое описание структуры иерархии базовых классов автотестов, разбиения проекта по функциональной модели java-packages и шаблоны-образцы реальных классов.
Статья будет полезна всем разработчикам, которые разрабатывают автотесты на базе Selenium.
Эта статья является частью общей публикации, в которой мы описывали, как небольшой командой выстраивали процесс автоматизации UI тестирования и разрабатывали для этого фреймворк на базе Junit и Selenium.
Предыдущие части:
- Часть 1. Организационно-управленческая. Зачем нам была нужна автоматизация. Организация процесса разработки и управления. Организация использования
- Часть 2. Техническая. Архитектура и технический стек. Детали реализации и технические сюрпризы
Реализация базового класса для всех тестов и 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;
}}
*/
}
В следующей части я расскажу о том, как мы реализовали процесс выгрузки файла из контейнера с браузером в тестовый фреймворк, и решили вопрос с поиском имени загруженного браузером файла.
Кстати, будем рады пополнить свою команду. Актуальные вакансии вот здесь.