Как спроектировать игровое программное обеспечение так, чтобы его можно было легко тестировать?

25

Практично ли использовать среду тестирования, такую ​​как JUnit, в ситуации разработки игры? Какими конструктивными соображениями вы можете следовать, чтобы сделать вашу игру более тестируемой? Какие части игры можно / нужно тестировать и какие части следует / нужно оставить для тестирования человеком?

Например, если игровой цикл заключен в одну функцию, кажется, что это будет ужасно сложно проверить. Мне нравится рефакторинг функции «обновления», которая занимает дельту времени и продвигает игровую логику; это позволяет использовать некоторые интересные трюки, такие как возможность замедлить игру, подкармливая ее фальшивыми медленными дельтами времени.

Ricket
источник
6
Зачем тратить человеко-часы на написание юнит-тестов, когда у вас есть практически бесконечная армия рабского труда, чтобы проводить игровые тесты для вас? Я шучу, шучу ...;)
Майк Штробель
1
На самом деле вам не нужен ребенок, это действительно хороший момент! Я не думал об этом, но игры - самый простой тип программного обеспечения, чтобы заставить других людей тестировать. Я предполагаю, что это несколько компенсирует сложность автоматического тестирования игры (юнит или нет).
Рикет
3
Модульное тестирование не обязательно означает, что ваше приложение работает правильно в целом (т.е. функциональное тестирование). Больше о том, чтобы будущие изменения не нарушали существующую функциональность. Конечно, пользователь может видеть, когда космический корабль перевернут, но когда алгоритм отслеживания отключен на 0,001, эффекты могут не проявиться, пока игра не будет сыграна в течение 200 часов. Такого рода вещи могут поймать модульные тесты еще до того, как код выйдет.
Уэйд Уильямс
1
Игры - это самое простое программное обеспечение, позволяющее пользователям играть , но играя! = Тестирование . Получить хорошие сообщения об ошибках довольно сложно. Кроме того, отслеживание того, где в коде происходит ошибка, и проверка того, что новые изменения не нарушают существующий код, значительно выигрывают от автоматических тестов.
Великолепно

Ответы:

25

Одним из принципов TDD является то, что вы позволяете TDD в некоторых случаях влиять на ваш дизайн. Вы пишете тест для системы, затем пишете код, чтобы сделать этот тест успешным, сохраняя зависимости как можно более мелкими.

Для меня есть только две вещи, которые я не тестирую в рамках модульного тестирования:

Во-первых, я не проверяю визуальные элементы и то, как все выглядит. Я проверяю это, и объект будет в нужном месте после обновления, что камера выберет объект за его пределами, что преобразования (по крайней мере, те, которые выполняются вне шейдеров) выполняются должным образом перед передачей графическому движку. , но как только он попадает в графическую систему, я рисую линию. Мне не нравится пытаться издеваться над такими вещами, как DirectX.

Во-вторых, я не проверяю основную функцию игрового цикла. Я проверяю, что каждая система будет работать, когда она прошла разумную дельту, и что системы работают вместе правильно, когда это необходимо. Затем я просто обновляю каждую систему правильной дельтой в игровом цикле. Я мог бы на самом деле провести тест, чтобы показать, что каждая система вызывается с правильной дельтой, но во многих случаях я нахожу это избыточным (если вы не выполняете сложную логику, чтобы получить свою дельту, то это не является избыточным).

Джефф
источник
6
Первый абзац является ключевым. Дело в том, что TDD требует, чтобы вы разрабатывали свой код лучше, чем без него, просто чтобы позволить тестирование. Конечно, многие думают, что они являются экспертами в дизайне, но, конечно, те, кто больше всего впечатлен их собственными навыками, как правило, те, кому нужно больше всего учиться ...
dash-tom-bang
2
Я не согласен с тем, что в результате код обязательно разрабатывается лучше. Я видел множество мерзостей, в которых нужно было открыть открытый интерфейс и нарушить инкапсуляцию, чтобы добавить тестируемые зависимости.
Kylotan
4
Я считаю, что это происходит чаще в тех случаях, когда тесты пишутся для уже существующих классов, а не наоборот.
Джефф
@Kylotan это скорее признак неправильного дизайна / плохого дизайна теста. Рефакторинг и использование mocks для создания чистых тестов, не взламывайте ваш код в худшее состояние; это противоположно тому, что должно было случиться.
Тимоксли
2
Инкапсуляция - это хорошо, но важнее понять, ПОЧЕМУ это хорошо. Если вы просто используете это, чтобы скрыть зависимости и большой уродливый интерфейс, это в значительной степени выкрикивает нагрузку, что ваш дизайн под капотом, вероятно, не очень хорош. Инкапсуляция в первую очередь не в том, чтобы скрывать уродливый код и зависимости, а в том, чтобы облегчить понимание вашего кода и дать потребителю меньше путей, чтобы следовать, когда он пытается рассуждать о коде.
Мартин Одхелиус
19

Ноэль Ллопис покрыл юнит-тестирование в той степени, в которой, я полагаю, вы ищете:

Что касается тестирования всего игрового цикла, есть еще один метод предотвращения функциональных регрессий в вашем коде. Идея состоит в том, чтобы ваша игра играла сама через систему воспроизведения (то есть сначала записывает входные данные игрока, а затем воспроизводит их во время другого прогона). Во время воспроизведения вы создаете снимок состояния каждого объекта для каждого кадра. Эту коллекцию состояний можно затем назвать «золотым образом». При рефакторинге вашего кода вы снова запускаете воспроизведение и сравниваете состояние каждого кадра с золотым состоянием изображения. Если он отличается, тест не пройден.

Хотя преимущества этого метода очевидны, есть несколько вещей, с которыми нужно быть осторожным:

  • Ваша игра должна быть детерминированной, поэтому вам нужно быть осторожным с такими вещами, как зависимость от системных часов, системных / сетевых событий, генераторов случайных чисел, которые не являются детерминированными. и т.п.
  • изменения содержимого после создания золотого изображения могут привести к законным различиям с вашим золотым изображением. Обойти это невозможно - при смене контента вам нужно восстановить свой золотой образ.
jpaver
источник
+1 Также стоит отметить, что ценность этого подхода напрямую связана с длиной «золотого образа»: если он недостаточно длинный, вы можете легко пропустить ошибки; если это будет слишком долго, вам придется много ждать. Важно попытаться заставить игру работать в этом режиме, возможно, на порядок быстрее, чем при обычных условиях, чтобы сократить время. Пропуск рендеринга может помочь!
инженер
9

Я согласен с комментариями Джеффа и Джапера. Я также хотел добавить, что принятие модели компонентов для вашей архитектуры значительно повышает ее тестируемость. В модели компонентов каждый компонент должен выполнять одну единицу работы и должен тестироваться изолированно (или с ограниченным количеством фиктивных объектов).

Аналогичным образом, другие части игры, которые зависят от сущностей, должны полагаться только на подмножество компонентов, чтобы функционировать. На практике это означает, что вы обычно можете смоделировать несколько поддельных компонентов, чтобы облегчить тестирование этих областей, или вы можете просто составить частичные объекты для целей тестирования. например, вы пропускаете компоненты рендеринга и ввода, потому что они не нужны для проверки физики.

Наконец, я бы проигнорировал совет относительно производительности, который вы получаете от некоторых людей в игровом сообществе относительно интерфейсов. Хорошо напишите код с интерфейсами, и если вы столкнетесь с проблемами производительности в будущем, вы можете легко профилировать, идентифицировать и реорганизовать для решения проблемы. Меня не убедили опасения Ноэля относительно влияния интерфейсов на производительность или сложность, которую они добавили.

Тем не менее, не переусердствуйте, пытаясь проверить каждую мелочь самостоятельно. Как и большинство вещей, тестирование и дизайн - это достижение правильного баланса.

Алекс Шеарер
источник
Обратите внимание, что, хотя вы, кажется, не новичок в SX, ответы не упорядочены, и что-то вроде «оба из вышеприведенных комментариев» обречено на быстрое осуждение, поскольку в настоящее время вы на один голос опережаете одного из двух других ответов на этот раз. :)
Рикет
Спасибо, я не думал об этом, комментируя. С тех пор я обновил комментарий, чтобы выбрать отдельные комментарии.
Алекс Шярер
3

Я подумал, что добавлю второй ответ в ответ на комментарий ОП о том, что пользователь может заменить модульные тесты. Я считаю, что это совершенно неправильно, поскольку цель юнит-тестов не в том, чтобы гарантировать качество. Если вам нужны тесты для обеспечения качества вашей программы, вы, вероятно, должны исследовать сценарии тестов или инвестировать в отличный мониторинг.

(С отличным мониторингом и продуктом, работающим как услуга, действительно возможно провести некоторое «тестирование» на клиенте, но вам нужна сложная система, которая быстро обнаруживает ошибки и откатывает ответственные за изменения. Скорее всего, это слишком много для небольшая команда разработчиков.)

Основное преимущество модульных тестов заключается в том, что они заставляют разработчика писать более качественный код. Акт тестирования заставляет разработчика разделять проблемы и инкапсулировать данные. Это также побуждает разработчика использовать интерфейсы и другие абстракции, чтобы он тестировал только одну вещь.

Алекс Шеарер
источник
Согласитесь, за исключением того, что модульные тесты не заставляют разработчиков писать хороший код. Есть еще один вариант: действительно плохо написанные тесты. Вам нужна приверженность идее модульных тестов от ваших кодеров, чтобы они не ломались.
Tenpn
2
Конечно, но это не причина, чтобы выбросить ребенка с водой из ванны. Используйте инструменты ответственно, но сначала используйте инструменты.
Алекс Шярер