Unity: Введение в юнит-тестирование

В данном уроке вы изучите все о том, как происходит юнит-тестирование в среде разработки Unity и как использовать его в проектах.

Версия: C# 7.2, Unity 2018.3, Unity

Интересно, как работает юнит-тестирование в Юнити? Не знакомы с тем, как в принципе работает юнит-тестирование? Если вы ответите «да» на эти вопросы, то этот урок для вас. Вы узнаете следующие о юнит-тестировании:

  • Что это такое.
  • Ценность, которую предлагает данный подход.
  • За и против.
  • Как это работает в Юнити с помощью Test Runner.
  • Написание и запуск юнит-тестирования.

Примечание. Этот урок предполагает, что вы уже знакомы с языком C# и базовой разработкой на Юнити. Если вы новичок в Unity, то ознакомьтесь с другими уроками на сайте.

Что такое юнит-тестирование?

Прежде чем погрузиться в код, важно иметь четкое представление о том, что такое юнит-тестирование. Проще говоря, юнит-тестирование — это тестирование юнитов.

Юнит-тестирование в идеале предназначено для тестирования одной «единицы» кода. Что именно составляет «единицу» различается, но важно помнить о том, что юнит-тест должен тестировать какой-то один «элемент» за раз.

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

Вы написали метод, который позволяет пользователю вводить имя. Вы написали метод таким образом, что в имени нельзя использовать числа, и имя не может быть больше десяти символов. Метод перехватывает каждое нажатие клавиши и добавляет этот символ к полю name, как показано ниже:

public string name = ""
public void UpdateNameWithCharacter(char: character)
{
    // 1
    if (!Char.IsLetter(char))
    {
        return;
    }

    // 2
    if (name.Length > 10)
    {
        return;
    }

    // 3
    name += character;
}

Вот что здесь происходит:

  1. Если символ не является буквой, то этот код выходит из функции заранее и не добавляет символ к строке.
  2. Если длина имени — десять символов или больше, то код не позволяет пользователю добавить еще один символ.
  3. После прохождения двух проверок код добавляет символ к концу имени.

Этот метод можно протестировать, потому что он выполняет «единицу» работы. Юнит-тесты реализуют логику метода.

Пример юнит-тестирования

Как бы вы написали юнит-тестирование для метода UpdateNameWithCharacter?

Перед тем, как начать, нужно хорошо подумать о том, что будут делать юнит-тесты, и придумать им названия.

Взгляните на имена методов тестов-примеров. Названия должны говорить о том, что именно тестируется:

UpdateNameDoesntAllowCharacterAddingToNameIfNameIsTenOrMoreCharactersInLength

UpdateNameAllowsLettersToBeAddedToName

UpdateNameDoesntAllowNonLettersToBeAddedToName

Из названий методов тестирования видно, что тестируется «единица» работы, которая выполняется UpdateNameWithCharacter, и делает то, что должна. Эти названия тестов могут показаться длинными и очень специфическими, но они полезны.

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

Набор тестов для юнит-тестирования в Unity

Запуск игры

Откройте проект Crashteroids Starter (можете скачать проект по кнопке «Скачать материалы урока» вверху данного урока) и откройте сцену Game в Assets / RW / Scenes.

Экран игры для юнит-тестирования в Unity

Нажмите на Play, чтобы запустить Crashteroids, а затем нажмите на кнопку Start Game. Используйте клавиши влево и вправо, чтобы двигать космический корабль влево и вправо.

Нажмите пробел, чтобы выстрелить лазером. Если лазер попадает в астероид, то счет увеличивается на единицу. Если астероид попадает в корабль, то корабль взрывается, и игра заканчивается (с возможностью начать заново).

Экран игры в Unity: корабль, который выстреливает лазером и астероиды

Попробуйте поиграть некоторое время, а затем убедитесь, что если в корабль врезается астероид, то экран «Game Over» срабатывает.

Экран завершения игры для юнит-тестирования в Unity

Начало работы с Unity Test Runner

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

Чтобы написать тесты, сначала нужно узнать, что такое Test Runner в Юнити. Test Runner позволяет запускать тесты и проверять, проходят ли они. Чтобы открыть Unity Test Runner выберите Window > General > Test Runner.

Как открыть Test Runner в верхнем меню Unity

После того, как Test Runner откроется в виде нового окна, вы можете упростить работу нажатием на окно Test Runner и перетащив его рядом с окном Scene.

Перетаскивание и закрепление окна Test Runner в Unity

Настройка NUnit и папок для тестов

Test Runner это функция юнит-тестирования, которая поставляется вместе с Юнити, но в ней используется фрэймворк NUnit. Когда вы станете более серьезно относиться к написанию юнит-тестов, вам следует подумать о прочтении вики по NUnit, чтобы узнать больше. На данный момент, все, что вам нужно знать, будет рассмотрено здесь.

Чтобы запустить тесты сначала нужно создать папку теста, чтобы сохранять в ней классы.

В окне Project выберите папку RW. Посмотрите на окно Test Runner и убедитесь, что установлен параметр PlayMode.

Нажмите на кнопку Create PlayMode Test Assembly Folder. Вы увидите новую появившуюся папку в папке RW. Имя по умолчанию Tests подходит, поэтому вы можете нажать Enter, чтобы назначить название.

Создание новой папки для юнит-тестирования в окне TestRunner в Unity

Вам может быть любопытно, что это за две разные вкладки внутри Test Runner.

Вкладка PlayMode предназначена для тестов, которые будут запускаться в режиме воспроизведения (как если бы играли в игру в реальном времени). Тесты EditMode будут запускаться вне режима воспроизведения, что отлично подходит для тестирования таких вещей, как настраиваемые поведения в окне Inspector.

В этом уроке мы сосредоточимся на тестах PlayMode. Но не стесняйтесь экспериментировать с тестированием, как только почувствуете, что готовы. Убедитесь, что вкладка PlayMode выбрана при работе с окном Test Runner.

Что находится в наборе тестов?

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

Test Runner будет проходить через все файлы классов с тестами и запускать юнит-тесты в них. Файл класса, который содержит юнит-тесты, называется набором тестов.

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

Подготовка тестовой сборки и набора тестов

Выберите папку Tests и в окне Test Runner нажмите на кнопку Create Test Script in current folder. Назовите новый файл «TestSuite».

Создание скрипта для юнит-тестирования в окне TestRunner в Unity

В дополнение к созданию нового файла C# движок Юнити также создает еще один файл с именем Tests.asmdef. Это расширение для файла сборки и оно используется, чтобы указать Unity, где располагаются файловые зависимости. Все для того, чтобы код сохранялся отдельно от кода для тестов.

Если возникает ситуация при которой Unity не может найти файлы тестов или тесты, дважды проверьте есть ли файл определения сборки, который включает набор тестов. Следующий шаг — настройка.

Чтобы убедиться, что тестовый код имеет доступ к игровым классам, нужно создать сборку кода класса и установить ссылку в сборке Tests. Нажмите на папку Scripts, чтобы выделить ее. Правой кнопкой мыши нажмите на эту папку и выберите Create > Assembly Definition.

Создание сборки кода класса для юнит-тестирования в Unity

Назовите файл «GameAssembly».

Файл сборки кода для юнит-тестирования в окне Project в Unity

Нажмите на папку Tests, затем на файл определения сборки Tests. В окне Inspector нажмите на кнопку с плюсом под заголовком Assembly Definition References.

Создание ссылки определения сборки для юнит-тестирования в окне Inspector в Unity

Вы увидите поле Missing Reference. Нажмите на точку рядом с полем, чтобы открыть окно выбора. Выберите файл GameAssembly.

Назначение файла определения сборки для юнит-тестирования в окне Assets в Unity

Теперь вы должны будете увидеть файл сборки GameAssembly в разделе ссылок. Нажмите на кнопку Apply, чтобы сохранить эти изменения.

Кнопка Apply для сохранения изменений настроек определения сборки юнит-тестирования в окне Inspector в Unity

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

Написание первого юнит-теста

Нажмите два раза на скрипт TestSuite, чтобы открыть его в редакторе кода. Замените весь код на следующий:

using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;

public class TestSuite
{


}

Какие тесты нужно писать? По правде говоря, даже в этой крошечной игре Crashteroids есть довольно много тестов, которые нужно написать, чтобы убедиться в том, что все работает должным образом. В данном уроке вы будете интересоваться только несколькими ключевыми областями, связанными с обнаружением попаданий и основной игровой механикой.

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

Для первого теста, было бы неплохо убедиться, что астероиды действительно движутся вниз. Астероидам будет действительно сложно повредить корабль, если они будут двигаться от него! Добавьте следующий метод и переменную с указанием private в скрипт TestSuite и сохраните:

private Game game;

// 1
[UnityTest]
public IEnumerator AsteroidsMoveDown()
{
    // 2
    GameObject gameGameObject = 
        MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
    game = gameGameObject.GetComponent<Game>();
    // 3
    GameObject asteroid = game.GetSpawner().SpawnAsteroid();
    // 4
    float initialYPos = asteroid.transform.position.y;
    // 5
    yield return new WaitForSeconds(0.1f);
    // 6
    Assert.Less(asteroid.transform.position.y, initialYPos);
    // 7
    Object.Destroy(game.gameObject);
}

Это только несколько строчек кода, но они многое проделывают. Поэтому, сделайте паузу и убедитесь, что понимаете каждый аспект кода:

  1. Это атрибут. Атрибуты определяют особое поведение компилятора. Данный атрибут сообщает компилятору Unity, что это юнит-тест. Это заставит отображаться код в Test Runner при запуске тестов.
  2. Создается экземпляр класса Game. Все находится в объекте game, поэтому, когда вы создаете этот объект, все, что нужно будет тестировать также находится здесь. В среде продакшена скорее всего игровые объекты не будут находиться только в одном префабе. Поэтому нужно будет позаботиться и повторно создать все объекты, необходимые в сцене.
  3. Здесь вы создаете астероид, чтобы отслеживать его движение. Метод SpawnAsteroid возвращает экземпляр созданного астероида. У компонента Asteroid есть метод Move (можете посмотреть на скрипт Asteroid в папке RW / Scripts, если интересно, как работает движение).
  4. Отслеживание исходного положения необходимо для того, чтобы убедиться в том, что астероид сдвинулся вниз.
  5. Все юнит-тесты Unity — это сопрограммы, поэтому нужно добавить yield return. Также добавить временной шаг в 0.1 секунды, чтобы имитировать течение времени, в течение которого астероид должен двигаться вниз. Если вам не нужна такое моделирование, то вы можете вернуть значение null.
  6. Это шаг утверждения, где происходит утверждение того, что позиция астероида меньше, чем исходная (это означает, что астероид сдвинулся вниз). Понимание утверждений является ключевой частью юнит-тестирования, и NUnit предоставляет различные методы утверждений. Прохождение или невыполнение теста определяется этой строчкой кода.
  7. Конечно, вас не наругают за оставленный беспорядок после завершения юнит-тестов, но другие тесты могут из-за этого не пройти. Всегда важно очищать (удалять или сбрасывать) код после юнит-теста с тем, чтобы при запуске следующего теста не было артефактов, которые могут повлиять на тест. Все что нужно сделать, это удалить игровой объект, поскольку для каждого теста вы создаете совершенно новый экземпляр game для следующего теста.

Прохождение тестов

Прекрасная работа! Вы написали свой первый юнит-тест, но как вы узнаете, что он работает? Конечно при помощи Test Runner! В окне Test Runner раскройте все стрелки. Вы должны видеть тест AsteroidsMoveDown в списке с серым кружком.

Список юнит-тестов в окне TestRunner в Unity

Серый кружок означает, что тест еще не выполнялся. Когда тест запускается и пройден, то показывается зеленая стрелка. Если тест не пройден, то отображается красный значок x. Запустите тест, нажав на кнопку RunAll.

Запуск первого юнит-теста в окне TestRunner в Unity

Будет создана временная сцена и запустится тест. Когда тест выполнится, вы увидите, что он пройден.

Выполнение юнит-теста в окне TestRunner в Unity

Вы успешно создали свой первый пройденный юнит-тест, который утверждает, что появляющиеся астероиды движутся вниз.

Примечание. Прежде чем написать свои собственные юнит-тесты, вам необходимо понимать, какую реализацию вы тестируете. Если вам интересно, как логика, которую вы тестируете, работает, то взгляните на код в папке RW / Scripts.

Использование интеграционных тестов

Прежде чем углубиться в кроличью нору юнит-тестов, сейчас самое время объяснить, что такое интеграционные тесты и чем они отличаются от юнит-тестирования.

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

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

Допустим, вы сделали боевую игру, где игрок убивает монстров. Возможно, вы захотите провести интеграционный тест, чтобы убедиться, что когда игрок убивает 100 юнитов, открывается достижение.

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

Интеграционный тест имитирует, что игрок убивает 100 монстров и удостоверяется, что достижение разблокировано. Это сильно отличается от юнит-теста, потому что тестируется совместная работа больших компонентов кода.

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

Добавление тестов в набор тестов

Следующий тест будет тестировать игру, когда корабль врезается в астероид. С открытым TestSuite в редакторе кода, добавьте следующий тест ниже первого юнит-теста и сохраните:

[UnityTest]
public IEnumerator GameOverOccursOnAsteroidCollision()
{
    GameObject gameGameObject = 
       MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
    Game game = gameGameObject.GetComponent<Game>();
    GameObject asteroid = game.GetSpawner().SpawnAsteroid();
    //1
    asteroid.transform.position = game.GetShip().transform.position;
    //2
    yield return new WaitForSeconds(0.1f);

    //3
    Assert.True(game.isGameOver);

    Object.Destroy(game.gameObject);
}

Вы уже видели большую часть этого кода в предыдущем тесте, но здесь есть несколько моментов:

  1. Вы вызываете крушение астероида и корабля, явно задавая астероиду то же положение, что и у корабля. Это заставит их столкнуться и вызовет завершение игры. Если вам интересно, как работает этот код, то посмотрите файлы Ship, Game, и Asteroid в папке Scripts.
  2. Временной шаг необходим для срабатывания события Collision физического движка, поэтому возвращается 0.1 секундное ожидание.
  3. Это истинное утверждение, и оно проверяет, установлен ли флаг gameOver в скрипте Game на значение true. Код игры работает с этим флагом, установленным на значение true, когда корабль разрушен, поэтому вы тестируете, установлено ли значение true после того, как корабль разрушен.

Вернитесь в окно Test Runner, чтобы увидеть новый список юнит-тестов.

Новый юнит-тест в окне TestRunner в Unity

В этот раз, вы будете только запускать тест, вместо всего набора тестов. Нажмите на GameOverOccursOnAsteroidCollision, затем нажмите на кнопку Run Selected.

Запуск выделенного юнит-теста в окне TestRunner в Unity

И вот, еще один тест пройден.

Второй юнит-тест пройден в окне TestRunner в Unity

Этапы настройки и завершения

Вы могли заметить, что между двумя тестами, в которых создается игровой объект Game, есть повторяющийся код и ссылка на то, где установлен скрипт Game:

GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
game = gameGameObject.GetComponent<Game>();

Вы также заметите это, когда игровой объект Game уничтожен:

Object.Destroy(game.gameObject);

Это очень часто встречается при тестировании. Фактически есть два этапа, когда дело доходит до запуска юнит-теста. Этап установки и этап разборки.

Любой код внутри метода Setup будет запускаться перед юнит-тестом (в этом наборе), и любой код в методе Tear Down будет запускаться после юнит-теста (в этом наборе).

Пришло время облегчить работу, передвинув код настройки и завершения в специальный метод. Откройте редактор кода и добавьте следующий код сверху файла TestSuite, просто над первым [UnityTest] атрибутом:

[SetUp]
public void Setup()
{
    GameObject gameGameObject = 
        MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
    game = gameGameObject.GetComponent<Game>();
}

Атрибут SetUp определяет, что этот метод вызывается раньше любого выполняемого теста.

Далее, добавьте следующий метод и сохраните:

[TearDown]
public void Teardown()
{
    Object.Destroy(game.gameObject);
}

Атрибут TearDown определяет, что этот метод вызывается после каждого выполнения теста.

С подготовленным кодом настройки и завершения, удалите строчки кода, которые находятся в этих методах, и замените их соответствующими вызовами методов. После этого код будет выглядеть так:

public class TestSuite
{
    private Game game;

    [SetUp]
    public void Setup()
    {
        GameObject gameGameObject = 
            MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
        game = gameGameObject.GetComponent<Game>();
    }

    [TearDown]
    public void Teardown()
    {
        Object.Destroy(game.gameObject);
    }

    [UnityTest]
    public IEnumerator AsteroidsMoveDown()
    {
        GameObject asteroid = game.GetSpawner().SpawnAsteroid();
        float initialYPos = asteroid.transform.position.y;
        yield return new WaitForSeconds(0.1f);
  
        Assert.Less(asteroid.transform.position.y, initialYPos);
    }

    [UnityTest]
    public IEnumerator GameOverOccursOnAsteroidCollision()
    {
        GameObject asteroid = game.GetSpawner().SpawnAsteroid();
        asteroid.transform.position = game.GetShip().transform.position;
        yield return new WaitForSeconds(0.1f);

        Assert.True(game.isGameOver);
    }
}

Тестирование Game Over и Laser Fire

Теперь, когда методы настройки и завершения готовы, чтобы упростить работу, самое время добавить еще несколько тестов, которые используют их. Следующий тест должен будет проверять, что значение переменной gameOver не равно true, когда игрок нажимает на кнопку New Game. Добавьте следующий тест вниз файла и сохраните:

[UnityTest]
public IEnumerator NewGameRestartsGame()
{
    //1
    game.isGameOver = true;
    game.NewGame();
    //2
    Assert.False(game.isGameOver);
    yield return null;
}

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

  1. Эта часть кода устанавливает тест так, чтобы для переменной gameOver было установлено значение true. Когда вызывается метод NewGame, он должен устанавливать этот флаг обратно на значение false.
  2. Здесь, вы утверждаете, что переменная isGameOver равна false, что должно происходить после вызова новой игры.

Вернитесь к Test Runner, чтобы увидеть, что там находится новый тест NewGameRestartsGame. Запустите этот тест, как делали раньше и посмотрите, что он проходит:

Новый юнит-тест в окне TestRunner в Unity

Утверждение движения лазера

Следующий добавленный тест будет проверять, что лазер, который выпускает корабль, движется вверх (аналогично первому юнит-тесту, который вы написали). Откройте файл TestSuite в редакторе. Добавьте следующий метод и затем сохраните:

[UnityTest]
public IEnumerator LaserMovesUp()
{
      // 1
      GameObject laser = game.GetShip().SpawnLaser();
      // 2
      float initialYPos = laser.transform.position.y;
      yield return new WaitForSeconds(0.1f);
      // 3
      Assert.Greater(laser.transform.position.y, initialYPos);
}

Вот что делает этот код:

  1. Берется ссылка на созданный лазер, вышедший из корабля.
  2. Исходная позиция записывается, так что вы можете подтвердить, что происходит движение вверх.
  3. Это утверждение просто, как и в юнит-тесте AsteroidsMoveDown, только теперь вы утверждаете, что значение больше (указывая на то, что лазер движется вверх).

Сохраните и вернитесь к Test Runner. Запустите тест LaserMovesUp и посмотрите, что он выполняется:

Юнит-тестирование движения лазера вверх в игре в окне TestRunner в Unity

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

Проверка того, что лазеры уничтожают астероиды

Далее, нужно убедиться в том, что лазер будет уничтожать астероид, если попадет в него. Откройте редактор и добавьте следующий тест внизу TestSuite и сохраните:

[UnityTest]
public IEnumerator LaserDestroysAsteroid()
{
    // 1
    GameObject asteroid = game.GetSpawner().SpawnAsteroid();
    asteroid.transform.position = Vector3.zero;
    GameObject laser = game.GetShip().SpawnLaser();
    laser.transform.position = Vector3.zero;
    yield return new WaitForSeconds(0.1f);
    // 2
    UnityEngine.Assertions.Assert.IsNull(asteroid);
}

Вот как это работает:

  1. Вы создаете астероид и лазер, и убеждаетесь, что у них одинаковая позиция для срабатывания коллизии.
  2. Специальный тест с важным отличием. Обратите внимание, как вы явно используете UnityEngine.Assertions для этого теста? Это потому, что в Unity есть специальный класс Null, который отличается от «обычного» класса Null. Утверждение Assert.IsNull() фрэймворка NUnit не будет работать для нулевых проверок Unity. При такой проверке в Unity вы должны явно использовать UnityEngine.Assertions.Assert, а не not the NUnit Assert.

Вернитесь к Test Runner и запустите новый тест. Вы увидите удовлетворительный зеленый значок.

Юнит-тестирование уничтожения астероидов лазером в игре в окне TestRunner в Unity

Тестировать или не тестировать?

Решение о переходе на юнит-тесты — это серьезное обязательство, и к нему нельзя относиться легкомысленно. Хотя вознаграждение, безусловно, того стоит. Существует даже методология разработки, известная как разработка через тестирование (Test Driven Development, TDD).

Используя TDD, вы фактически пишите тесты, прежде чем написать логику приложения. Сначала вы проводите тесты, проверяете, что они не работают, и только затем пишите код, предназначенный для прохождения теста. Это может быть совершенно другой подход к программированию, но это также гарантирует, что вы написали код тестируемым способом.

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

Примечание. Вам необходимо принять решение тестировать только общедоступные методы или также приватные. Некоторые люди считают, что приватные методы нужно тестировать только через общедоступные методы, которые их используют. Это может сделать «единицу» тестируемого кода очень большой, что нежелательно. С другой стороны, тестирование приватных методов может быть проблематичным и потребует специальных фреймворков или использования инструментов рефлексии для проверки. У каждого сценария есть свои плюсы и минусы, которые выходят за рамки данного урока. В этом уроке все методы будут протестированы как общедоступные, чтобы упростить выполнение задач, поэтому не используйте этот урок как практическое руководство, когда дело касается продакшен кода.

Тестирование может быть серьезным обязательством, поэтому стоит взглянуть на плюсы и минусы добавления модульного тестирования в ваш проект:

Плюсы юнит-тестирования

У модульного тестирования есть много важных преимуществ, в том числе следующие:

  • Обеспечивает уверенность в том, что метод ведет себя должным образом.
  • Служит документацией для новых людей, изучающих кодовую базу (юнит-тесты — отличное обучение).
  • Заставляет писать код проверяемым способом.
  • Помогает быстрее выявлять ошибки и быстрее их исправлять.
  • Предотвращает добавление новых ошибок в старый рабочий код в будущих обновлениях (известных как ошибки регрессии).

Минусы юнит-тестирования

Однако у вас может не быть времени или бюджета, чтобы заняться юнит-тестированием. Вы должны учитывать следующие недостатки:

  • Написание тестов может занять больше времени, чем написание самого кода.
  • Плохие или неточные тесты создают ложную уверенность.
  • Требуется больше знаний для правильной реализации.
  • Важные части базы кода могут быть нелегко протестированы.
  • Некоторые фреймворки не позволяют легко проводить тестирование приватных методов, что может усложнить юнит-тестирование.
  • Если тесты слишком хрупкие (слишком легко терпят неудачу по неправильным причинам), обслуживание может занять много времени.
  • Юнит-тесты не обнаруживают ошибок интеграции.
  • Пользовательский интерфейс сложно протестировать.
  • Неопытные разработчики могут тратить время на тестирование неправильных вещей.
  • Иногда тестирование с внешними зависимостями или зависимостями времени выполнения может быть очень сложным.

Тестирование того, что при уничтожении астероидов увеличивается счет

Пришло время написать последний тест. В открытом редакторе добавьте следующий код в файле TestSuite и сохраните:

[UnityTest]
public IEnumerator DestroyedAsteroidRaisesScore()
{
    // 1
    GameObject asteroid = game.GetSpawner().SpawnAsteroid();
    asteroid.transform.position = Vector3.zero;
    GameObject laser = game.GetShip().SpawnLaser();
    laser.transform.position = Vector3.zero;
    yield return new WaitForSeconds(0.1f);
    // 2
    Assert.AreEqual(game.score, 1);
}

Это важный тест, который гарантирует, что, когда игрок уничтожает астероид, счет увеличивается. Вот как это происходит:

  1. Вы создаете астероид и лазер, проверяете, что они находятся в одной позиции. Это гарантирует, что произойдет столкновение, которое приведет к увеличению игрового счета.
  2. Это означает, что переменная game.score равна 1 (вместо нуля, который был в начале).

Сохраните код и возвращайтесь к Test Runner, чтобы запустить этот последний тест и посмотреть, что он выполняется:

Юнит-тестирование увеличения счета за уничтоженные астероиды в игре в окне TestRunner в Unity

Прекрасная работа! Все тесты выполняются.

Куда двигаться дальше?

Здесь было рассмотрено много вопросов. Если хотите сравнить свою работу с финальным проектом, то можете найти ссылку на скачивание в начале.

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

Чувствуете уверенность? Вы можете написать еще много тестов. Попробуйте просмотреть все файлы классов игры и написать юнит-тесты для других частей кода. Рассмотрите возможность добавления тестов для следующих сценариев:

  • Каждый тип астроида запускает завершение игры, когда врезается в корабль.
  • Начало новой игры устанавливает счет на ноль.
  • Движение корабля влево и вправо работает правильно.

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

Также прочтите документацию NUnit, чтобы узнать больше о фреймворке NUnit.

Если вам понравился урок, то добавляйте его в закладки социальных сетей (значки снизу), а также делайте репосты, чтобы поделиться новыми знаниями. Успешного тестирования!

Автор перевода: Jean Winters

Источник: Introduction To Unity Unit Testing

Комментировать

Почта не публикуется.Обязательные поля отмечены *