Upgrade to Pro — share decks privately, control downloads, hide ads and more …

CodeFest 2019. Артём Разинов (Avito) — E2E UI-т...

CodeFest
April 06, 2019

CodeFest 2019. Артём Разинов (Avito) — E2E UI-тесты: много, зелено и на Pull Request

Расскажу, как начать писать функциональные UI-тесты в iOS, сократить время регрессионного тестирования большого приложения с дней до часов, получить стабильный и зеленый на 95% test suite, запуск тестов на каждый Pull Request, как прийти в дальнейшем к 100% зеленым UI-тестам. Расскажу про техники, приемы, алгоритмы — они универсальны и будут полезны каждому. Доклад посвящен iOS, тем не менее, большинство практик применимы к Android и Web.

CodeFest

April 06, 2019
Tweet

More Decks by CodeFest

Other Decks in Technology

Transcript

  1. Вводное Как плохо было Как хорошо стало О тестах Хардкорное

    Как обойтись без автотестов Перезапуски Trusted тесты Импакт анализ Сравнение с таргет веткой Заключение Заключение !2
  2. Как плохо было • 10 дней на первую итерацию "регресса"

    • Много ручного тестирования • О проблемах узнавали только на регрессе • Большое время от бранчката до раскатки на 100% • Релиз (хорошо если) раз в месяц !4
  3. Сроки выполнения первой итерации ? !7 ⬤ основные релизы ⬤

    автотесты ⬤ объем обязательных тестов ⬤ минорные релизы
  4. ? Время выполнения первой итерации !8 ⬤ потрачено времени на

    ручную работу ⬤ автотесты ⬤ объем обязательных тестов
  5. !11

  6. О тестах PR Регресс Всего девайсов 1 3 Всего тестов

    247 2313 Всего запусков 255 3285 Среднее время запуска 37.549с 59.998с Сумма длительностей 2ч 39м 35с (9575с) 54ч 44м 54с (197094с) Реальное время тестов 4м 41с (281с) 1ч 33м 31с (5611с) Реальное время сборки 10м 36с (636с) 1ч 37м 0с (5820с) Параллелизация 34,07 35,13 Накладные расходы 5м 55с (355с) 3м 29с (209с) !12
  7. О тестах PR Регресс Всего девайсов 1 3 Всего тестов

    247 2313 Всего запусков 255 3285 Среднее время запуска 37.549с 59.998с Сумма длительностей 2ч 39м 35с (9575с) 54ч 44м 54с (197094с) Реальное время тестов 4м 41с (281с) 1ч 33м 31с (5611с) Реальное время сборки 10м 36с (636с) 1ч 37м 0с (5820с) Параллелизация 34,07 35,13 Накладные расходы 5м 55с (355с) 3м 29с (209с) !13
  8. Зеленость полного суита Сломались тесты Начали активно писать тесты Нагрузка

    на CI Улучшение
 раннера Фиксы
 фрейморка Trusted tests Рестарты Тесты, на которые плевать !18
  9. Виды тестов по красноте Вид Как лечить Полный суит PR

    Стабильные ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ - - Иногда флакующие ✔ ✔ ✔ ✔ ✔ ✘ ✔ ✔ ✔ Перезапуски Перезапуски Очень нестабильные ✘ ✔ ✘ ✘ ✘ ✔ ✘ ✘ ✘ Перезапуски Trusted tests Нестабильная инфра ✔ ✔ ✔ ✘ ✘ ✘ ✔ ✔ ✔ Перезапуски Target Branch Сломанные ✔ ✔ ✔ ✔ ✘ ✘ ✘ ✘ ✘ Pull Request Trusted tests Изначально нерабочие ✘ ✘ ✘ ✘ ✘ Pull Request Импакт анализ Брошенные ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘ Уведомления Trusted tests !19
  10. Виды тестов по красноте Вид Как лечить Полный суит PR

    Стабильные ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ - - Иногда флакующие ✔ ✔ ✔ ✔ ✔ ✘ ✔ ✔ ✔ Перезапуски Перезапуски Очень нестабильные ✘ ✔ ✘ ✘ ✘ ✔ ✘ ✘ ✘ Перезапуски Trusted tests Нестабильная инфра ✔ ✔ ✔ ✘ ✘ ✘ ✔ ✔ ✔ Перезапуски Target Branch Сломанные ✔ ✔ ✔ ✔ ✘ ✘ ✘ ✘ ✘ Pull Request Trusted tests Изначально нерабочие ✘ ✘ ✘ ✘ ✘ Pull Request Импакт анализ Брошенные ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘ ✘ Уведомления Trusted tests !20
  11. Перезапуски Success Rate 1 2 3 4 5 6 50

    % 50 % 75 % 87,5 % 93,75 % 96,875 % 98,4375 % 80 % 80 % 96 % 99,2 % 99,84 % 99,968 % 99,9936 % 95 % 95 % 99,75 % 99,9875 % 99,9994 % 100 % 100 % 99 % 99 % 99,99 % 99,9999 % 100 % 100 % 100 % !24
  12. Перезапуски Success Rate 1 2 3 4 5 6 50

    % 50 % 75 % 87,5 % 93,75 % 96,875 % 98,4375 % 80 % 80 % 96 % 99,2 % 99,84 % 99,968 % 99,9936 % 95 % 95 % 99,75 % 99,9875 % 99,9994 % 100 % 100 % 99 % 99 % 99,99 % 99,9999 % 100 % 100 % 100 % !25
  13. Перезапуски Success Rate 1 2 3 4 5 6 50

    % 50 % 75 % 87,5 % 93,75 % 96,875 % 98,4375 % 80 % 80 % 96 % 99,2 % 99,84 % 99,968 % 99,9936 % 95 % 95 % 99,75 % 99,9875 % 99,9994 % 100 % 100 % 99 % 99 % 99,99 % 99,9999 % 100 % 100 % 100 % !26
  14. Перезапуски Success Rate 1 2 3 4 5 6 50

    % 50 % 75 % 87,5 % 93,75 % 96,875 % 98,4375 % 80 % 80 % 96 % 99,2 % 99,84 % 99,968 % 99,9936 % 95 % 95 % 99,75 % 99,9875 % 99,9994 % 100 % 100 % 99 % 99 % 99,99 % 99,9999 % 100 % 100 % 100 % !27
  15. Перезапуски Success Rate 1 2 3 4 5 6 50

    % 50 % 75 % 87,5 % 93,75 % 96,875 % 98,4375 % 80 % 80 % 96 % 99,2 % 99,84 % 99,968 % 99,9936 % 95 % 95 % 99,75 % 99,9875 % 99,9994 % 100 % 100 % 99 % 99 % 99,99 % 99,9999 % 100 % 100 % 100 % !28
  16. Когда лежит инфраструктура Test 1 ✘ ✘ ✘ ✔ Infra

    1 Test 2 ✔ Infra 2 Ждем часик-другой !31
  17. !39

  18. Перезапуски (1 тест) Success Rate 1 2 3 4 5

    6 50 % 50 % 75 % 87,5 % 93,75 % 96,875 % 98,4375 % 80 % 80 % 96 % 99,2 % 99,84 % 99,968 % 99,9936 % 95 % 95 % 99,75 % 99,9875 % 99,9994 % 100 % 100 % 99 % 99 % 99,99 % 99,9999 % 100 % 100 % 100 % !41
  19. Перезапуски (1 тест) Success Rate 1 2 3 4 5

    6 50 % 50 % 75 % 87,5 % 93,75 % 96,875 % 98,4375 % 80 % 80 % 96 % 99,2 % 99,84 % 99,968 % 99,9936 % 95 % 95 % 99,75 % 99,9875 % 99,9994 % 100 % 100 % 99 % 99 % 99,99 % 99,9999 % 100 % 100 % 100 % !42
  20. Перезапуски (250 тестов) Success Rate 1 2 3 4 5

    6 50 % 0 % 0 % 0 % 0 % 0,0357 % 1,9505 % 80 % 0 % 0,0037 % 13,4251 % 67,0105 % 92,3105 % 98,4127 % 95 % 0,0003 % 53,4843 % 96,9231 % 99,8439 % 99,9922 % 99,9996 % 99 % 8,1059 % 97,5309 % 99,975 % 99,9998 % 100 % 100 % !43
  21. Перезапуски (500 тестов) Success Rate 1 2 3 4 5

    6 50 % 0 % 0 % 0 % 0 % 0 % 0,038 % 80 % 0 % 0 % 1,8023 % 44,9041 % 85,2122 % 96,8506 % 95 % 0 % 28,6057 % 93,9409 % 99,688 % 99,9844 % 99,9992 % 99 % 0,657 % 95,1227 % 99,95 % 99,9995 % 100 % 100 % !44
  22. Перезапуски (1000 тестов) Success Rate 1 2 3 4 5

    6 50 % 0 % 0 % 0 % 0 % 0 % 0 % 80 % 0 % 0 % 0,0325 % 20,1638 % 72,6112 % 93,8003 % 95 % 0 % 8,1828 % 88,249 % 99,3769 % 99,9688 % 99,9984 % 99 % 0,0043 % 90,4833 % 99,9 % 99,999 % 100 % 100 % !45
  23. Success Rate 1 2 3 4 5 6 50 %

    0 % 0 % 0 % 0 % 0 % 0 % 80 % 0 % 0 % 0,0325 % 20,1638 % 72,6112 % 93,8003 % 95 % 0 % 8,1828 % 88,249 % 99,3769 % 99,9688 % 99,9984 % 99 % 0,0043 % 90,4833 % 99,9 % 99,999 % 100 % 100 % Перезапуски (1000 тестов) !46
  24. Success Rate 1 2 3 4 5 6 50 %

    0 % 0 % 0 % 0 % 0 % 0 % 80 % 0 % 0 % 0,0325 % 20,1638 % 72,6112 % 93,8003 % 95 % 0 % 8,1828 % 88,249 % 99,3769 % 99,9688 % 99,9984 % 99 % 0,0043 % 90,4833 % 99,9 % 99,999 % 100 % 100 % Перезапуски (1000 тестов) !47
  25. Trusted Tests Build 997 Build 998 Build 999 Build 1000

    Trusted? ✘ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✘ ✘ ✘ ✘ ✘✔ ✔ ✔ ✘ ✔ ✘ размер окна: 3 билда минимально билдов: 2 !48
  26. Build 997 Build 998 Build 999 Build 1000 Trusted? ✘

    ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✘ ✘ ✘ ✘ ✘✔ ✔ ✔ ✘ ✔ ✘ Trusted Tests размер окна: 3 билда минимально билдов: 2 !49
  27. Build 997 Build 998 Build 999 Build 1000 Trusted? ✘

    ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✘ ✘ ✘ ✘ ✘✔ ✔ ✔ ✘ ✔ ✘ Trusted Tests размер окна: 3 билда минимально билдов: 2 !50
  28. Build 997 Build 998 Build 999 Build 1000 Trusted? ✘

    ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✘ ✘ ✘ ✘ ✘✔ ✔ ✔ ✘ ✔ ✘ Trusted Tests размер окна: 3 билда минимально билдов: 2 !51
  29. Увеличиваем количество данных ночь ночь ночь ночь ночь ночь iPhone

    7 iPhone SE iPhone X iPhone 7 iPhone SE iPhone X Trusted? ✔ ✔ ✔ ✔ ✔ ✔ !54 ночь ночь ночь ночь ночь ночь iPhone 7 iPhone SE iPhone X iPhone 7 iPhone SE iPhone X Trusted? ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✘ ✔ ✔ ✘ ✔ ✘ Используем запуски на всех девайсах:
  30. Улучшаем стабильность ночь ночь ночь ночь ночь ночь iPhone 7

    iPhone SE iPhone X iPhone 7 iPhone SE iPhone X Trusted? ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ ✔ !55 ночь ночь ночь день день день iPhone 7 iPhone SE iPhone X iPhone 7 iPhone SE iPhone X Trusted? ✔ ✔ ✔ ✘ ✔ ✘ ✘ ✔ ✔ ✔ ✔ ✘ ✘ ✘ Делаем запуски в течение рабочего дня:
  31. Фейл теста Фейл симулятора Фейл нагрузки Trusted? ✔ ✔ SKIP

    ✔ ✔ SKIP ✔ ✔ Фейл теста Фейл симулятора Фейл нагрузки Trusted? ✔ ✔ ✘ ✘ ✔ ✘ ✔ ✘ Увеличиваем количество трастед тестов !56 Игнорируем "шум":
  32. Сравнение с таргет веткой Ветка PR ✘ ✘ ✘ Инфра

    Целевая ветка ✘ !61 Из этого следует, что статус теста не зависит от изменений в коде
  33. Сравнение с таргет веткой Source * 5 Target Final ✔

    ✔ ✘ ✔ ✘ ✘ ✘ SKIP ✔ SKIP ✔ ✘ SKIP ✘ !62
  34. Сравнение с таргет веткой Source * 5 Target Final ✔

    ✔ ✘ ✔ ✘ ✘ ✘ SKIP ✔ SKIP ✔ ✘ SKIP ✘ !63
  35. Сравнение с таргет веткой Source * 5 Target Final ✔

    ✔ ✘ ✔ ✘ ✘ ✘ SKIP ✔ SKIP ✔ ✘ SKIP ✘ !64
  36. Сравнение с таргет веткой Source * 5 Target Final ✔

    ✔ ✘ ✔ ✘ ✘ ✘ SKIP ✔ SKIP ✔ ✘ SKIP ✘ !65
  37. Сравнение с таргет веткой Source * 5 Target Final ✔

    ✔ ✘ ✔ ✘ ✘ ✘ SKIP ✔ SKIP ✔ ✘ SKIP ✘ !66
  38. Сравнение с таргет веткой Ветка PR ✘ ✘ ✘ ✘

    ✘ Инфра Целевая ветка ✔ Ветка PR ✘ ✘ ✘ ✔ Инфра Целевая ветка ✔ !68 Как стало:
  39. Сравнение с таргет веткой Source * 3 Target Source *

    2 Final ✔ ✔ ✘ ✔ ✔ ✔ ✘ ✔ ✘ ✘ ✘ ✘ SKIP ✘ SKIP ✔ ✔ ✘ SKIP ✘ ✘ !69
  40. Сравнение с таргет веткой Source * 2 Target Source *

    3 Final ✔ ✔ ✘ ✔ ✔ ✔ ✘ ✔ ✘ ✘ ✘ ✘ SKIP ✘ SKIP ✔ ✔ ✘ SKIP ✘ ✘ !70
  41. Что еще нужно для зеленых тестов • Качественный код тестов,

    как если бы вы писали продакшен код: идеальный, хороший, чистый код • Фреймворк, толерантный к изменениям в UI • Раннер, толерантный к различным неприятностям • CI, идеальный, хороший, чистый • Стабильная инфраструктура
  42. Польза • 10 дней на регрессионное тестирование => 1 день

    • Нерегулярные релизы раз в месяц => регулярные раз в 2 недели • Много ручного тестирования => все пишут автотесты • Поломанные фичи не мержатся в общую ветку • Можно после больших рефакторингов за 1.5 часа получить результаты автотестов !80
  43. Цена • 2 человекогода на iOS специфичный CI и инструментарий

    • Уже готовый сервис отчетов, интеграционное API, ручки к интеграционному API, вся инфраструктура для этого • И еще много работы, которую я забыл упомянуть (типа Test Analyzer) • Будьте готовы к тому, что тесты не будут 100% зелеными • Пожары на CI и массовая блокировка пулреквестов обязательно будут !81
  44. Q&A

  45. Инструменты Код тестов Swift (84000 строк) Код CI Bash, Python,

    Swift Фреймворк тестов Mixbox (на основе XCTest/XCUI) Раннер Emcee (на основе fbxtest) CI Teamcity !85
  46. Интеграционное API Описание инструментрария. Swift, CI, Python, Mixbox Про нестабильную

    инфраструктуру. Мы люди простые Не хотим лезть в 250 сервисов. Подсветить то, о чем говорю. Сказать: Видео будет доступно. Презентация будет доступна. !86 let пользователь2 = интеграционноеАпи.пользователь { фильтр in фильтр.пользователь.город = .москва фильтр.объявление.город = .краснодар фильтр.объявление.категория = .вакансии фильтр.объявление.цена = 100000 фильтр.объявление.статус = .активно } мессенджер .сессия(пользователь1) .чат(пользователь2.объявление.первое) .отправитьСообщение("Актуально?") .отправитьПропущенныйВызов()
  47. Параметризация func test_dataSet0() { // ... }
 
 func test_dataSet1()

    { // ... }
 
 func test_dataSet2() { // ... }
 
 func test_dataSet3() { // ... } !87
  48. Параметризация func test_dataSet3() { parametrizedTest( dataSet: PublishParametersDataSetItem( menuSteps: PublishMenuSteps(.job, .cv,

    nil, .adminJob, nil), parameters: PublishParametersDataSetItem.Parameters( title: "Водитель дивана", description: "Закончить первую повесть Достоевскому мешал её герой, не желав price: "5", displayedPrice: "5", city: "Икряное", educationField: "Среднее", workSchedule: "Сменный график", workExperience: "10", gender: "Женский", businessTrip: "Не готов", relocation: "Возможен", national: "Казахстан", age: "99" ) ) ) } !88
  49. !90 Page Object Element public var anonymousNumberSwitch: ViewElement { return

    element("переключатель анонимного номера") { element in element.type == .switch && element.isSubviewOf { element in element.id == "anonymousNumber" } } }
  50. !91 Page Object if pageObjects.paparazzoScreen.confirmLibraryButton.isDisplayed() { pageObjects.paparazzoScreen.confirmLibraryButton.tap() } else {

    pageObjects.paparazzoScreen.nextButton.tap() } pageObjects.publishStepScreen.titleField.setText("Клавиатура") pageObjects.publishStepScreen.nextButton.tap() pageObjects.publishStepScreen.categoryCell(title: "Клавиатуры и мыши").tap() pageObjects.publishStepScreen.categoryCell(title: "Продаю своё").tap() pageObjects.publishStepScreen.nextButton.tap()
  51. !94

  52. !96