Наш підрозділ створює повністю автоматичні пайплайни для виведення нових версій додатків до прод-середовища. Зрозуміло, для цього потрібні функціональні автоматизовані тести. Під катом - історія про те, як, розпочавши з тестування в один потік на локальній машині, ми дійшли до багатопоточного запуску автотестів на Selenoid у пайплайні збірки з Allure-звітом на GitLab pages і в результаті отримали крутий інструмент для автоматизації, який зможуть використати майбутні команди.
З чого ми починали
Щоб реалізувати автотести та вбудувати їх у пайплайн, нам був потрібний фреймворк для автоматизації, який можна гнучко змінювати під наші потреби. В ідеалі хотілося отримати єдиний стандарт для двигуна автотестування, пристосований під вбудовування автотестів у пайплайн. Для реалізації ми обрали такі технології:
- Ява,
- Мейвен,
- селен,
- Cucumber+JUNIT 4,
- Allure,
- GitLab.
Чому саме такий набір? Java - одна з найпопулярніших мов для автотестів, до того ж ним володіють усі члени команди. Selenium – очевидне рішення. Cucumber також мав підвищити довіру до результатів автотестів з боку підрозділів, що займаються ручним тестуванням.
Однопотокові тести
Щоб не винаходити велосипеда, за основу фреймворку ми взяли напрацювання з різних репозиторіїв на GitHub і адаптували їх під себе. Створили репозиторій для головної бібліотеки з ядром фреймворку автотестів та репозиторій із Gold-прикладом реалізації автотестів на нашому ядрі. Кожна команда мала брати Gold-образ і розробляти у ньому тести, адаптуючи під свій проект. Розгорнули у банку GitLab-CI, на якому налаштували:
- щоденні запуски всіх написаних автотестів за кожним проектом;
- запуски в пайплайні збирання.
Спершу тестів було мало, і вони йшли в один потік. Однопотоковий запуск на Windows-раннері GitLab нас цілком влаштовував: тести дуже незначно навантажували тестовий стенд і майже не утилізували ресурси.
Згодом автотестів ставало дедалі більше, і ми задумалися над їхнім паралельним запуском, коли повний прогін почав займати близько трьох годин. З'явилися й інші проблеми:
- ми не могли переконатися, що тести стабільні;
- тести, які проходили по кілька прогонів поспіль на локальній машині, іноді падали до CI.
Приклад налаштування автотестів:
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<skipTests>${skipTests}</skipTests>
<testFailureIgnore>false</testFailureIgnore>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
-Dcucumber.options="--tags ${TAGS} --plugin io.qameta.allure.cucumber2jvm.AllureCucumber2Jvm --plugin pretty"
</argLine>
</configuration>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
<plugin>
<groupId>io.qameta.allure</groupId>
<artifactId>allure-maven</artifactId>
<version>2.9</version>
</plugin>
</plugins>
Приклад Allure-звіту
Навантаження на раннер під час тестів (8 ядер, 8 ГБ ОЗП, 1 потік)
Плюси однопотокових тестів:
- легко налаштувати та запустити;
- запуски у CI практично не відрізняються від локальних запусків;
- тести не афектять одне одного;
- мінімальні вимоги до ресурсів раннера.
Мінуси однопотокових тестів:
- дуже довго виконуються;
- тривала стабілізація тестів;
- неефективне використання ресурсів раннера, вкрай низька утилізація.
Тести на форках JVM
Оскільки при реалізації базового фреймворку ми не подбали про thread-safe код, найочевиднішим способом паралельного запуску став
Selenoid-сервер підняли на машині з 32 ядрами та 24 ГБ ОЗУ. Ліміт встановили в 48 браузерів - 1,5 потоку на ядро та близько 400 МБ ОЗУ. У результаті час тестів скоротився з трьох годин до 40 хвилин. Прискорення прогонів допомогло вирішити проблему стабілізації: тепер ми могли швидко проганяти нові автотести 20–30 разів, доки переконаємося, що вони виконуються стабільно.
Першим недоліком рішення стала висока утилізація ресурсів раннерів при невеликій кількості паралельних потоків: на 4 ядрах та 8 ГБ ОЗУ тести працювали стабільно не більше ніж у 6 потоків. Другий мінус: плагін генерує класи-раннери для кожного сценарію, хоч би скільки їх запускалося.
Важливо! Не прокидайте змінну з тегами в argLineнаприклад, так:
<argLine>-Dcucumber.options="--tags ${TAGS} --plugin io.qameta.allure.cucumber2jvm.AllureCucumber2Jvm --plugin pretty"</argLine>
…
Mvn –DTAGS="@smoke"
Якщо передавати тег таким чином, плагін генеруватиме раннери для всіх тестів, тобто намагатиметься запустити всі тести, пропускаючи їх відразу після запуску і створюючи при цьому безліч форків JVM.
Правильно змінну з тегом прокидати в теги у налаштуваннях плагіна, див. приклад нижче. В інших перевірених нами способах виникають проблеми із підключенням плагіна Allure.
Приклад часу прогону 6 коротких тестів із неправильним налаштуванням:
[INFO] Total time: 03:17 min
Приклад часу прогону тестів, якщо безпосередньо передавати тег у mvn … –Dcucumber.options:
[INFO] Total time: 44.467 s
Приклад налаштування автотестів:
<profiles>
<profile>
<id>parallel</id>
<build>
<plugins>
<plugin>
<groupId>com.github.temyers</groupId>
<artifactId>cucumber-jvm-parallel-plugin</artifactId>
<version>5.0.0</version>
<executions>
<execution>
<id>generateRunners</id>
<phase>generate-test-sources</phase>
<goals>
<goal>generateRunners</goal>
</goals>
<configuration>
<tags>
<tag>${TAGS}</tag>
</tags>
<glue>
<package>stepdefs</package>
</glue>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<configuration>
<forkCount>12</forkCount>
<reuseForks>false</reuseForks>
<includes>**/*IT.class</includes>
<testFailureIgnore>false</testFailureIgnore>
<!--suppress UnresolvedMavenProperty -->
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar" -Dcucumber.options="--plugin io.qameta.allure.cucumber2jvm.AllureCucumber2Jvm TagPFAllureReporter --plugin pretty"
</argLine>
</configuration>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
Приклад Allure-звіту (найнестабільніший тест, 4 рерани)
Навантаження раннера під час тестів (8 ядер, 8 ГБ ОЗП, 12 потоків)
Плюси:
- просте налаштування - потрібно лише додати плагін;
- можливість одночасного виконання великої кількості тестів;
- прискорення стабілізації тестів завдяки п.1.
Мінуси:
- потрібно кілька ОС/контейнерів;
- високе споживання ресурсів за кожен форк;
- плагін застарів і більше не підтримується.
Як перемогти нестабільність
Тестові стенди не ідеальні, як самі автотести. Не дивно, що у нас з'явилася кілька flacky-тестів. На допомогу прийшов
Приклад налаштування автотестів:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.21.0</version>
<configuration>
….
<rerunFailingTestsCount>2</rerunFailingTestsCount>
….
</configuration>
</plugin>
Або під час запуску: mvn … -Dsurefire.rerunFailingTestsCount=2 …
Як варіант – встановити опції Maven для скрипта PowerShell (PS1):
Set-Item Env:MAVEN_OPTS "-Dfile.encoding=UTF-8 -Dsurefire.rerunFailingTestsCount=2"
Плюси:
- не потрібно витрачати час на аналіз нестабільного тесту, коли він падає;
- можна згладити проблеми стабільності тестового стенду.
Мінуси:
- можна пропустити плаваючі дефекти;
- час прогону зростає.
Паралельні тести з бібліотекою Cucumber 4
Кількість тестів зростала з кожним днем. Ми знову замислилися про прискорення прогонів. Крім того, хотілося вбудувати в пайплайн складання програми якомога більше тестів. Критичним фактором стала дуже довга генерація раннерів при запуску в паралель за допомогою Maven-плагіну.
На той момент уже вийшов Cucumber 4, тож ми вирішили переписати ядро під цю версію. У release notes нам обіцяли паралельний запуск на рівні тредів. Теоретично це мало:
- значно прискорити прогін автотестів з допомогою збільшення кількості потоків;
- виключити втрату часу на генерацію раннерів кожного автотесту.
Оптимізувати фреймворк для багатопотокових автотестів виявилося не так складно. Cucumber 4 проганяє кожен окремий тест у виділеному потоці від початку і до кінця, тому деякі загальні static-речі були просто перетворені на ThreadLocal-змінні.
Головне при конвертації засобами рефакторингу Idea — перевірити місця, де відбувалося порівняння змінної (наприклад, перевірка на null). Крім того, потрібно винести Allure-плагін в інструкції класу Junit Runner.
Приклад налаштування автотестів:
<profile>
<id>parallel</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<useFile>false</useFile>
<testFailureIgnore>false</testFailureIgnore>
<parallel>methods</parallel>
<threadCount>6</threadCount>
<perCoreThreadCount>true</perCoreThreadCount>
<argLine>
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
</argLine>
</configuration>
<dependencies>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</build>
</profile>
Приклад Allure-звіту (найнестабільніший тест, 5 реранів)
Навантаження раннера під час тестів (8 ядер, 8 ГБ ОЗУ, 24 потоки)
Плюси:
- низьке споживання ресурсів;
- нативна підтримка Cucumber — не потрібні додаткові інструменти;
- можливість запуску понад 6 потоків на ядро процесора.
Мінуси:
- потрібно стежити, щоб код підтримував багатопоточне виконання;
- збільшується поріг входження.
Allure-звіти в GitLab pages
Після впровадження багатопотокового запуску ми почали витрачати на аналіз звітів набагато більше часу. На той момент нам доводилося викладати кожен звіт як артефакт у GitLab, потім завантажувати його, розпаковувати. Це не дуже зручно та довго. А якщо хтось ще хоче подивитися звіт у себе, то йому потрібно буде виконати ті самі операції. Нам хотілося отримувати Фідбек швидше, і вихід знайшовся - GitLab pages. Ця вбудована функція, яка доступна з коробки у всіх останніх версіях GitLab. Дозволяє деплоїти статичні сайти у себе на сервері та отримувати до них доступ за прямим посиланням.
Усі скріншоти з Allure-звітами зроблено у GitLab pages. Скрипт для деплою звіту на GitLab pages — Windows PowerShell (перед цим необхідно виконати автотести):
New-Item -ItemType directory -Path $testresulthistory | Out-Null
try {Invoke-WebRequest -Uri $hst -OutFile $outputhst}
Catch{echo "fail copy history"}
try {Invoke-WebRequest -Uri $hsttrend -OutFile $outputhsttrnd}
Catch{echo "fail copy history trend"}
mvn allure:report
#mvn assembly:single -PzipAllureReport
xcopy $buildlocationtargetsiteallure-maven-plugin* $buildlocationpublic /s /i /Y
Що в підсумку
Отже, якщо ви думали про те, чи потрібен вам Thread safe-код у Cucumber-фреймворку автотестів, тепер відповідь очевидна — з Cucumber 4 її просто впровадити, значно збільшивши тим самим кількість потоків, що запускаються одночасно. При такому способі запуску тестів питання вже стоїть про продуктивність машини з Selenoid і тестового стенду.
Практика показала, що запуск автотестів на тредах дозволяє звести витрату ресурсів до мінімуму при найкращій продуктивності. Як видно з графіків, збільшення потоків у 2 рази не призводить до аналогічного прискорення проходження тестів продуктивності. Тим не менш, ми змогли додати до складання програми більше 200 автоматичних тестів, які навіть з 5 реранами виконуються приблизно за 24 хвилини. Це дозволяє отримувати від них швидкий фідбек, а при необхідності вносити правки і повторювати процедуру знову.
Джерело: habr.com