Всем привет, меня зовут Александр Мастюгин, я работаю тестировщиком в студии Норд. В IT-сфере бытует предубеждение, что работа тестировщиком — утомительное и монотонное занятие.
Но я с этим не согласен: на мой взгляд, это творческая, техническая и исследовательская деятельность.
Чтобы хорошо выполнить эту работу, нужно погрузиться в задачу, разобраться во всех ее тонкостях, сложностях, разобраться, какие подводные камни в ней есть.
Но справедливости ради надо сказать, что есть еще скучный момент – это регресс.
Чтобы минимизировать его роль в рабочем процессе и, соответственно, избавиться от рутины, мы в студии Nord решили автоматизировать регрессионное тестирование.
В этом тексте я расскажу вам, что мы сделали.
Автоматизация механики автобатлера
Мы разрабатываем игру Hustle Castle. Это мобильный автобатлер с элементами экономической стратегии и РПГ.В качестве движка используется Unity, а сервер написан на Java. Основная механика автобоя такова: игрок и отряды противника противостоят друг другу, все персонажи имеют специальное снаряжение, дающее им способности, а сам бой автоматический — пользователю остается только произносить заклинания и использовать таланты своего героя.
В Hustle Castle также есть замок с кучей комнат, где можно добывать ресурсы, создавать предметы и так далее.
Также в игре присутствует сетевая механика с кланами, развитием территорий, ареной и многим другим.
И все это сосуществует друг с другом и подчинено общей логике.
С описанием Hustle Castle мы закончили, теперь можно перейти к более глубоким вещам.
Геймплей в игре завязан на способностях юнитов.
С технической точки зрения способность — это некая сущность, имеющая множество настроек: как и когда она будет активирована, на кого она повлияет, какой эффект она имеет, какие еще способности она может активировать и так далее.
Мы описываем поведение каждой способности в виде объектов Json. Есть простые способности, а есть структурные — они позволяют последовательно или параллельно активировать другие способности.
Кроме того, существуют различные типы способностей, такие как контратаки, баффы, оглушения.
В общем, там много разных настроек.
Все это учитывается алгоритмом автобоя.
Если сильно упростить, то вот как это работает. На входе есть какие-то данные — это состояние наших бойцов: какие у них характеристики, какие у них способности.
Переносим эти данные в калькулятор боя, который делает все расчеты.
На каждом шаге расчетов он сообщает, какое событие произошло, активировал ли он какую-либо способность или нанес ли урон.
После полного цикла вычислений получаем результат боя.
Стоит отметить, что код калькулятора боя доступен как на клиенте, так и на сервере.
Это необходимо для проверки результатов боя — оказывается, что и клиент, и сервер, и калькулятор боя смотрят на одни и те же данные.
Для автоматизации автоматической проверки боя мы решили сделать собственный клиент, который сможет на основе входящих данных формировать запрос к серверу на получение начального состояния боя.
Автотесты используют этот клиент, получают состояние, отправляют его в калькулятор, подписываются на событие нового шага расчета, а затем проверяют условия на каждом шаге.
Средний тест нашей боевой системы Для проверки механики боевой системы мы используем способности, собранные специально для тестирования.
Испытания направлены на механику; мы не проверяем способности к продажам тестами.
Мы также используем этот подход в нашем внутреннем продукте под названием «Battle Chaser».
Нашим геймдизайнерам это нужно для баланса боевой системы.
Геймдизайнер может взять из производства любые состояния игрока, объединить их в тестовые группы, при желании заменить одни способности на другие и провести тестовый прогон на большом количестве боёв.
По результатам прогона геймдизайнер получает статистику боя.
Это был первый шаг к автоматизации.
Наши автотесты дают нам уверенность в том, что базовая логика калькулятора боя не сломалась после действий геймдизайнеров и разработчиков.
Это сложная вещь, для которой, к сожалению, у нас нет юнит-тестов.
Поэтому эти автоматические тесты боевого калькулятора — наша единственная возможность убедиться, что большая часть боевой системы работает стабильно.
Тесты на сервере и клиенте
Чтобы объяснить, что будет дальше, обратимся к теории — перед вами всем известная пирамида тестирования.
Суть пирамиды проста.
Чем ближе мы ко дну, тем дешевле и быстрее тесты.
И наоборот – чем выше, тем дольше и дороже.
Решение кажется очевидным — нужно использовать юнит-тесты как самые быстрые и дешевые.
Но все немного сложнее.
Чтобы разработчики могли писать модульные тесты, приложение должно иметь определенный дизайн — оно должно быть тестируемым.
И, к сожалению, у нас это есть не везде.
Модульных тестов для серверной логики почти нет, а те, что существуют, представляют собой компонентные тесты, которые в основном охватывают подбор игроков, базовую логику режимов и функций.
Если брать сервер отдельно, то имеем следующую ситуацию.
На нашем сервере нет интеграционных тестов.
Тесты API теоретически можно написать, поскольку клиент и сервер общаются по протоколу protobuf. Это значит, что есть описание протокола, можно брать клиента и отправлять запросы.
Но пока мы держим эту идею про запас.
А что насчет клиента? Там дела обстоят несколько трагичнее.
Здесь нет ни модульных, ни компонентных тестов.
Итак, мы оказываемся на вершине пирамиды — нам остается только протестировать наше приложение через UI. Большая часть нашей игры выглядит так: много кнопок, диалогов, всплывающих окон, еще диалогов, еще кнопок.
Почти все элементы интерфейса живут на Canvas.
Вот как выглядят разные экраны в Hustle Castle В качестве базового инструмента мы взяли решение с открытым исходным кодом.
АльтЮнитиТестер это драйвер, который обеспечивает:
- Поиск объектов с помощью x-path;
- Управление сценой;
- Имитация способов ввода (касание, прокрутка, перетаскивание и т. д.);
- Вызов методов и получение свойств игровых объектов;
- Протокол связи через веб-сокет, позволяющий добавлять множество других команд.
; В итоге мы взяли Java, Allure, TestNG, решили использовать паттерн Page-Object и начали писать тесты.
Поначалу получилось очень круто и круто.
Мы написали около 10-15 базовых тестов, которые просто ходили по интерфейсу и что-то делали.
Однако быстро стало ясно, что наша кодовая база содержит ряд проблем, которые будут становиться все более и более проблематичными по мере роста проекта.
Первое было связано с селекторы .
На снимке экрана ниже показан пример того, как мы использовали Page-Object. Поля класса были селекторами, а методы содержали вызовы драйвера и дополнительную логику.
Проблема заключалась не только в том, что это выглядело массивно, но и в том, что все наши классы были доступны API AltUnity. И если разработчики что-то изменят в новой версии, нам будет мучительно больно обновляться.
Другая проблема заключалась в ответственность Page-Objects .
Во-первых, внутри Page-Object мы напрямую подтянули драйвер (привет, API!).
Во-вторых, объекты могут выполнять сложную логику.
В-третьих, наши Page-Objects знали о других Page-Objects — то есть сами перемещались по объектам.
Наши объекты страниц выглядели примерно так Другая проблема заключалась в внедрение зависимости .
Когда занятий было мало, все было хорошо.
Но поскольку тесты усложнялись, приходилось подключать кучу зависимостей, а также держать в голове, какие именно есть.
Вот как выглядят зависимости типичного теста: Большое количество зависимостей вызывает ненужные трудности: например, если в компанию придет новый человек и попробует написать автотест, ему нужно будет изучить все многообразие API, составить мысленную карту, какие классы у нас есть и как они связаны между собой и прилагают много усилий, чтобы погрузиться в процесс.
Сами тесты выглядели очень запутанными и трудными для понимания.
И последняя проблема, с которой мы столкнулись, это дублирование кода .
Например, на рисунке выше есть метод OpenShopAndBuyRoom, который является приватным для этого тестового класса, поэтому мы не можем использовать его где-либо еще.
Но так как мы стремимся написать больше тестов, нам хочется как-то повторно использовать этот метод и он должен принадлежать какому-то классу.
Время остановиться и подумать
Использование AltUnityTester и шаблона Page-Object очень похоже на автоматизацию разработки веб-приложений.Наши коллеги там используют Selenium WebDriver. А если мы возьмем концепции из сети и перенесем их в нашу предметную область, то получим:
- UnityDriver — взаимодействие с игрой.
- Unity-Object — это структурный шаблон для описания диалогов, экранов и сцен.
Мы используем их только для описания структуры, а вся логика обрабатывается по шагам.
- Unity-Element — кнопки, картинки, диалоги, текст и так далее.
Вообще все, что есть на сцене в Unity, — это UnityElement.
Мы также использовали шаблон «Шаги», чтобы отделить логику тестирования от объектов UnityObject. В результате мы получили фреймворк, с помощью которого мы можем:
- Разделите сущности на отдельные классы (Button, Label, AbstractDialog и т. д.).
- Установите x-путь элементов пользовательского интерфейса с помощью аннотаций @FindBy, а также введите новые аннотации и расширения.
- Создавайте отдельные блоки элементов и повторно используйте их в разных диалогах, осуществляя поиск объектов в контексте другого объекта.
- Создайте представления компонентов в Unity на тестовой стороне (поскольку объект может иметь несколько компонентов).
- Используя шаги, напишите тесты с точки зрения бизнес-логики игры («Открыть магазин», «Купить товар» и так далее).
- Код AltUnity находится глубоко в ядре, а драйвер скрыт за интерфейсом.
Это дает нам возможность писать тесты с точки зрения бизнес-процесса.
Например, «На локации — открыть казарму», «В казарме — улучшить казарму», «Взять отряд — переместить его в казарму».
А уже под капотом есть перетаскивание, клики и все остальное.
Вторая особенность шагов заключается в том, что их можно использовать повторно в будущем.
И не только в рамках функциональных тестов.
Например, недавно необходимо было реализовать запуск одного сценария на множестве разных состояний игрока.
Создали новый проект, подключили библиотеку с шагами, несколько строк кода для запуска скрипта — задача выполнена.
Итак, ниже находится объект Unity. Помните, как выглядели наши селекторы? Они были ужасно уродливы.
Теперь мы просто используем аннотации, в которых указываем, как искать нужный элемент и всё.
Пример типизированного элемента Вот так выглядит описание практически любого диалога в нашем проекте.
При этом шаги имеют доступ к кнопкам с возможностью нажимать на них, спискам повторяющихся объектов, а также шаги могут получать информацию из всего диалога (сколько у нас золота, какие слоты открыты или закрыты и так далее).
).
Инициализацией полей класса занимается загрузчик элементов Unity — он получает определенный класс и драйвер.
По некоторой логике мы создаем прокси-элементы для каждого поля в классе.
И таким образом мы можем просто написать «Нажать кнопку», хотя на самом деле система сначала найдет эту кнопку, информация об этом вернется обратно, и только после этого будет отправлена команда «Нажать».
Ниже вы можете видеть, что у нас есть шаги для диалогового окна квеста.
И эти шаги описаны в терминах самой игры.
Пример теста Вот так выглядят все тесты.
Мы используем только одну инъекцию самих шагов.
На основе него мы пишем в терминах бизнес-логики то, что хотим сделать.
В итоге все выглядит вполне аккуратно.
Планы на будущее
Наши главные планы — провести еще больше тестов.Все эти усилия были направлены на расширение нашей кодовой базы удобным, простым и понятным способом.
Но впереди нас ждет проблема — многопоточность.
На данный момент тесты выполняются в одном потоке для одного экземпляра игры.
Все работает хорошо, но это занимает много времени.
Чтобы справиться с этим, мы можем создать несколько экземпляров на нашем удаленном сервере.
Или можем собрать ферму устройств и подключиться к ним.
Но некоторые наши функции являются «глобальными» и могут мешать прохождению тестов.
Например, если «Портал для фермерства» открыт, то он открыт для всех.
А при открытии или закрытии «Портала» возникают уведомления, которые могут появиться в интерфейсе параллельно работающего теста, и тап произойдет случайно по уведомлению, а не по нужному элементу.
Следующее, что мы хотели бы реализовать — это back-to-back тесты: это когда вы берете две версии приложения, запускаете один и тот же скрипт и делаете скриншоты в определенный момент. А потом сравниваешь.
Таким образом вы сможете проверить, работает ли у вас что-то, появилась ли функция раньше времени и так далее.
На данный момент мы расширяем охват функционала, у нас есть дым-сет как минимум с одним тестом на любой аспект игры, а также мы начали обучать коллег написанию тестов.
Фреймворк для автоматизации тестирования — это тот же продукт. Он должен быть простым, понятным и легко расширяемым.
При его проектировании не следует забывать о закономерностях и принципах разработки программного обеспечения, а также пренебрегать рефакторингом.
В противном случае спасение себя от регрессии станет вашей головной болью при сопровождении вас.
Иногда стоит сделать шаг назад, чтобы завтра сделать три шага вперед. Теги: #Разработка игр #gamedev #qa #java #unity #Тестирование игр #автоматизация контроля качества
-
Представляем A9.Com
19 Oct, 24 -
Педагогическая Психология
19 Oct, 24 -
Как Создавать 3D-Модели Видеоигр
19 Oct, 24 -
Почувствуйте Пульс Земли
19 Oct, 24