Примеры автоматизированного тестирования игр в Unity

Содержание

Слайд 2

Слайд 3

Ситуации Новый функционал ломает старый или старый функционал бесследно исчезает QA

Ситуации

Новый функционал ломает старый
или старый функционал бесследно исчезает
QA перегружен
или QA

отсутствует
“Всё сломалось! *** ***!”
в мягкой форме: “Я уже заказал вам пиццу”
"Это всегда так работало!"
если разобраться: "Это сломалось два месяца назад. Я написал в чатик, но мне никто не ответил"
Команда встала, потому что основная ветка разработки разломана
а виновник уехал на Бали
Неожиданно перед релизом все узнали что билд вылез за 100 мб
за 100 мб он вылез три месяца назад и сейчас весит 150 мб
маркетинг уже закупил трафик
Слайд 4

Слайд 5

Ситуации Новый функционал ломает старый или старый функционал бесследно исчезает QA

Ситуации

Новый функционал ломает старый
или старый функционал бесследно исчезает
QA перегружен
или QA

отсутствует
“Всё сломалось! *** ***!”
в мягкой форме: “Я уже заказал вам пиццу”
"Это всегда так работало!"
если разобраться: "Это сломалось два месяца назад. Я написал в чатик, но мне никто не ответил"
Команда встала, потому что основная ветка разработки разломана
а виновник уехал на Бали
Неожиданно перед релизом все узнали что билд вылез за 100 мб
за 100 мб он вылез три месяца назад и сейчас весит 150 мб
Слайд 6

КАК?

КАК?

Слайд 7

ПО КУСОЧКАМ!

ПО КУСОЧКАМ!

Слайд 8

Четыре кусочка Этап 1. Непрерывная интеграция Этап 2. Интеграционное тестирование Этап

Четыре кусочка

Этап 1. Непрерывная интеграция
Этап 2. Интеграционное тестирование
Этап 3. Тестирование скриншотами
Этап

4. Performance-тестирование
Слайд 9

ЭТАП 1 Непрерывная интеграция

ЭТАП 1
Непрерывная интеграция

Слайд 10

Непрерывная интеграция. Continuous Integration Сделать что-то по расписанию или по какому-либо

Непрерывная интеграция. Continuous Integration

Сделать что-то по расписанию или по какому-либо событию
Собрать

ночную сборку и залить в HockeyApp
Прогнать тесты на каждый коммит в репозиторий
Хранит билды, результаты тестов и метрики
Множество готовых решений
Слайд 11

Процесс Коммит в репозиторий Continuous Integration Unity Test Runner Edit Mode

Процесс

Коммит в репозиторий
Continuous Integration
Unity Test Runner Edit Mode тесты (Unit-тесты)
Unity Test

Runner Play Mode тесты (Интеграционные тесты в редакторе)
Сборка билда для каждой платформы (WebGL, Android, iOS)
Unity Test Runner Play Mode тесты (Интеграционные на устройстве)
Деплой (по требованию)
Слайд 12

Результаты первого этапа. Непрерывная интеграция Основная ветка всегда в рабочем состоянии

Результаты первого этапа.
Непрерывная интеграция

Основная ветка всегда в рабочем состоянии
Нет ошибок

в редакторе
Билд для каждой платформы
Знаем когда билд вышел за пределы необходимых размеров
История из собранных билдов позволяет быстро сравнить различные билды между собой
Слайд 13

ЭТАП 2 Интеграционное тестирование

ЭТАП 2
Интеграционное тестирование

Слайд 14

Интеграционное тестирование Взаимодействие модулей Бизнес-логика Можно проводить на реальных устройствах Дополнение к ручному тестированию и unit-тестам

Интеграционное тестирование

Взаимодействие модулей
Бизнес-логика
Можно проводить на реальных устройствах
Дополнение к ручному тестированию и

unit-тестам
Слайд 15

Unity Test Runner

Unity Test Runner

Слайд 16

Тест-кейс. Игра BFG.

Тест-кейс. Игра BFG.

Слайд 17

Пример теста [ UnityTest ] public IEnumerator ShopWindowTest() { // ->

Пример теста

[ UnityTest ] public IEnumerator ShopWindowTest() { // -> .. создание gameProvider, viewProvider yield

return gameProvider.StartWithCustomScene( "TestFrameworkExample" ); yield return viewProvider.ClickButton< ViewMainMenuExample >( "Shop" ); yield return viewProvider.WaitView< ViewShopExample >(); yield return viewProvider.ClickButton< ViewShopExample >( "Close" ); yield return gameProvider.CleanUpGame(); }
Слайд 18

Запуск сцены [ UnityTest ] public IEnumerator ShopWindowTest() { // ->

Запуск сцены

[ UnityTest ] public IEnumerator ShopWindowTest() { // -> .. создание gameProvider, viewProvider yield

return gameProvider.StartWithCustomScene( "TestFrameworkExample" ); yield return viewProvider.ClickButton< ViewMainMenuExample >( "Shop" ); yield return viewProvider.WaitView< ViewShopExample >(); yield return viewProvider.ClickButton< ViewShopExample >( "Close" ); yield return gameProvider.CleanUpGame(); }
Слайд 19

TestElement - помечаем поля public class ViewShopExample : MonoBehaviour { [

TestElement - помечаем поля

public class ViewShopExample : MonoBehaviour { [ SerializeField ] [ TestElement(

"Close" ) ] private Button _closeButton; [ TestElement( "OffersCount" ) ] private int _offersCount; } // использование viewProvider.ClickButton< ViewShopExample >( "Close" ) viewProvider.GetElementFromView< ViewShopExample, int >( "OffersCount" )
viewProvider.GetElementByName< ViewShopExample, int >( "_offersCount" )
Слайд 20

Нажимаем на кнопку [ UnityTest ] public IEnumerator ShopWindowTest() { //

Нажимаем на кнопку

[ UnityTest ] public IEnumerator ShopWindowTest() { // -> .. создание gameProvider,

viewProvider yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" ); yield return viewProvider.ClickButton< ViewMainMenuExample >( "Shop" ); yield return viewProvider.WaitView< ViewShopExample >(); yield return viewProvider.ClickButton< ViewShopExample >( "Close" ); yield return gameProvider.CleanUpGame(); }
Слайд 21

Способы нажать на кнопку в Unity UI Внешний - API операционной

Способы нажать на кнопку в Unity UI

Внешний - API операционной системы
Работает

не только на кнопки, но и на все приложение
Для каждой платформы нужно писать реализацию
Внутренний - вызываем событие onClick
Работает только для кнопок Unity UI
Работает на всех платформах
Прокликивание сквозь другие UI элементы
Внутренний - переопределение BaseInput
Работает для всех элементов Unity UI
Работает для любой платформы
Эмулируем курсор и касания из кода
Слайд 22

Эмуляция курсора в Unity UI

Эмуляция курсора в Unity UI

Слайд 23

Assert yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" ); yield return viewProvider.ClickButton ( "Shop"

Assert

yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" ); yield return viewProvider.ClickButton< ViewMainMenuExample >( "Shop" ); yield

return viewProvider.WaitView< ViewShopExample >(); Assert.AreEqual( 100500, viewProvider.GetElementFromView< ViewShopExample, int >( "OffersCount" ) );
yield return viewProvider.ClickButton< ViewShopExample >( "Close" ); yield return gameProvider.CleanUpGame();
Слайд 24

Делаем скриншоты GameViewUtils.SetResolution( 800, 600 ); yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" );

Делаем скриншоты

GameViewUtils.SetResolution( 800, 600 ); yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" ); yield return viewProvider.ClickButton<

ViewMainMenuExample >( "Shop" ); yield return viewProvider.WaitView< ViewShopExample >();
Assert.AreEqual( 100500, viewProvider.GetElementFromView< ViewShopExample, int >( "OffersCount" ) ); yield return screenshotHelper.CreateScreenshot( "Shop" ); yield return viewProvider.ClickButton< ViewShopExample >( "Close" ); yield return gameProvider.CleanUpGame();
Слайд 25

Результаты этапа 2. Интеграционное тестирование Запускаемость игры Основной игровой луп Дополнительная

Результаты этапа 2. Интеграционное тестирование

Запускаемость игры
Основной игровой луп
Дополнительная логика
Можно определить состояние

билда просто посмотрев на скриншоты
Слайд 26

ЭТАП 3 Тестирование скриншотами

ЭТАП 3
Тестирование скриншотами

Слайд 27

Сравнение скриншотов GameViewUtils.SetResolution( 800, 600 ); yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" );

Сравнение скриншотов

GameViewUtils.SetResolution( 800, 600 ); yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" ); var beforeShop =

screenshotHelper.CreateScreenshot( "MainMenu" ); yield return beforeShop; yield return viewProvider.ClickButton< ViewMainMenuExample >( "Shop" ); yield return viewProvider.WaitView< ViewShopExample >();
Assert.AreEqual( 100500, viewProvider.GetElementFromView< ViewShopExample, int >( "OffersCount" ) ); yield return screenshotHelper.CreateScreenshot( "Shop" ); yield return viewProvider.ClickButton< ViewShopExample >( "Close" ); var afterShop = screenshotHelper.CreateScreenshot( "MainMenu" ); yield return afterShop; var diffImagePath = screenshotHelper.GetPath( "MainMenuDiffAfterShopOpened" ); var pixelsChanged = ImageComparer.Compare( beforeShop.Path, afterShop.Path, diffImagePath ); Assert.AreEqual( 0, pixelsChanged ); yield return gameProvider.CleanUpGame();
Слайд 28

Сравнение скриншотов. Результат. PASSED FAILED

Сравнение скриншотов. Результат.

PASSED

FAILED

Слайд 29

Итоговый результат GameViewUtils.SetResolution( 800, 600 ); yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" );

Итоговый результат

GameViewUtils.SetResolution( 800, 600 ); yield return gameProvider.StartWithCustomScene( "TestFrameworkExample" ); var beforeShop =

screenshotHelper.CreateScreenshot( "MainMenu" ); yield return beforeShop; yield return viewProvider.ClickButton< ViewMainMenuExample >( "Shop" ); yield return viewProvider.WaitView< ViewShopExample >(); yield return screenshotHelper.CreateScreenshot( "Shop" ); yield return viewProvider.ClickButton< ViewShopExample >( "Close" ); var afterShop = screenshotHelper.CreateScreenshot( "MainMenu" ); yield return afterShop; Assert.AreEqual( 100500, viewProvider.GetElementFromView< ViewShopExample, int >( "OffersCount" ) ); var diffImagePath = screenshotHelper.GetPath( "MainMenuDiffAfterShopOpened" ); var pixelsChanged = ImageComparer.Compare( beforeShop.Path, afterShop.Path, diffImagePath ); Assert.AreEqual( 0, pixelsChanged ); yield return gameProvider.CleanUpGame();
Слайд 30

Тестирование скриншотами. Шейдеры Источник: https://simonschreibt.de/wft/watchdog-compare/

Тестирование скриншотами. Шейдеры

Источник: https://simonschreibt.de/wft/watchdog-compare/

Слайд 31

Тестирование скриншотами. Алгоритмы

Тестирование скриншотами. Алгоритмы

Слайд 32

Тестирование скриншотами. Сравнение с оригиналом Храним оригинал скриншота в репозитории Сравниваем

Тестирование скриншотами.
Сравнение с оригиналом

Храним оригинал скриншота в репозитории
Сравниваем новые скриншоты против

оригинала
Если есть изменения между новыми скриншотами и старыми
Решение 1 - новые скриншоты это и есть новый оригинал
Решение 2 - открываем баг
Слайд 33

Результаты этапа 3. Тестирование скриншотами Из кода сложно понять, что игра

Результаты этапа 3. Тестирование скриншотами

Из кода сложно понять, что игра выглядит

неправильно
Человек справляется лучше со сравниванием скриншотов, если он знает куда смотреть
Компьютер может сравнить скриншоты и принять по этому поводу решение, либо просто показать разницу человеку, который будет принимать решение
Слайд 34

ЭТАП 4 Performance-тестирование

ЭТАП 4
Performance-тестирование

Слайд 35

Performance-тестирование Собираем метрики во время тестов Память Производительность Скорость загрузки Миллион

Performance-тестирование

Собираем метрики во время тестов
Память
Производительность
Скорость загрузки
Миллион других параметров
Строим график по этим

метрикам
Слайд 36

Скриншот Performance-тестирования

Скриншот Performance-тестирования

Слайд 37

Результаты этапа 4. Performance-тестирование Известные метрики об игре Известен момент когда

Результаты этапа 4. Performance-тестирование

Известные метрики об игре
Известен момент когда что-то пошло

не так
Проще принять решение готов билд к релизу или нет
Слайд 38

Выводы Поэтапно вводим интеграционное тестирование Этап 1. Непрерывная интеграция Этап 2.

Выводы

Поэтапно вводим интеграционное тестирование
Этап 1. Непрерывная интеграция
Этап 2. Интеграционное тестирование
Этап 3.

Тестирование скриншотами
Этап 4. Performance-тестирование
Автоматизируем процесс тестирования
Быстрое время реагирования, если что-то идет не так
Экономим деньги (баг найденный раньше стоит намного дешевле)
Увеличиваем количество Smoke-тестов (кардинально)
Меньше неожиданностей
Команда знает о состоянии билда
У программиста есть страховочная сеть
Можно осуществить на любой стадии проекта
Слайд 39

Полезные ссылки https://www.gdcvault.com/play/1025013/Tools-Tutorial-Day-Tools-to (GDC, Amy Phillips, Tools to Reduce Open Bug

Полезные ссылки

https://www.gdcvault.com/play/1025013/Tools-Tutorial-Day-Tools-to (GDC, Amy Phillips, Tools to Reduce Open Bug Count

at Media Molecule)
http://gdcvault.com/play/1022784/Fast-Iteration-Tools-in-the (GDC, Alen Ladavac, Fast Iteration Tools in the Production of the Talos Principle)
https://www.youtube.com/watch?v=ff5LNHGBGoM (DataArt, Валентин Анопренко, Интеграционные автотесты бизнес-логики)
https://simonschreibt.de/wft/watchdog/ (Simon Schreibt, Using screenshot comparing techniques)
https://www.youtube.com/watch?v=ULwdj_Vr_WA (HolyJS, Роман Дворнов, Unit-тестирование скриншотами: преодолеваем звуковой барьер)
https://www.youtube.com/watch?v=LEy3_2ZzWpk (DotNext, Андрей Акиньшин, Поговорим про Performance-тестирование)
Слайд 40

Бонус 1. Улучшаем плей-тесты Увеличиваем Time.timeScale (не применимо для всех тестов/приложений)

Бонус 1. Улучшаем плей-тесты

Увеличиваем Time.timeScale (не применимо для всех тестов/приложений)
Не должны

зависеть от рандома
Узкая функциональность
Нам не важна производительность кода теста (поэтому мы активно используем рефлексию в тестах)
Monkey Runner
Использовать функционал ботов и/или реплеев
Слайд 41

Бонус 2. Что хотелось бы видеть в Unity Возможность экспортировать результаты

Бонус 2. Что хотелось бы видеть в Unity

Возможность экспортировать результаты тестов

с устройства
Возможность фильтровать тесты при запуске из консоли
Возможность фильтровать тесты при запуске на устройстве
Play-тесты в Unity Cloud Build
Code Coverage
Слайд 42

Бонус 3. Альтернативы. SikuliX Источник: http://sikulix.com/

Бонус 3. Альтернативы. SikuliX

Источник: http://sikulix.com/

Слайд 43

Бонус 4. Альтернативы. Unium Источник: https://assetstore.unity.com/packages/tools/unium-automated-test-tools-95998

Бонус 4. Альтернативы. Unium

Источник: https://assetstore.unity.com/packages/tools/unium-automated-test-tools-95998