Attention: Here be dragons

This is the latest (unstable) version of this documentation, which may document features not available in or compatible with released stable versions of Godot.

Модульне тестування

Godot Engine дозволяє писати модульні тести безпосередньо на C++. Двигун інтегрує фреймворк модульного тестування doctest, який дає можливість писати набори тестів та тестові випадки поруч із продакшн-кодом, але оскільки тести в Godot проходять через іншу точку входу main, вони знаходяться у спеціальному каталозі tests/, який розташований у корені вихідного коду движка.

Платформа та цільова підтримка

Модульні тести C++ можна запускати в операційних системах Linux, macOS і Windows.

Тести можна виконувати лише з увімкненими інструментами редактора, що означає, що шаблони експорту наразі не можна перевірити.

Запуск тестів

Перш ніж можна буде фактично запустити тести, двигун має бути скомпільовано з увімкненою опцією збірки tests (і будь-якою іншою опцією збирання, яку ви зазвичай використовуєте), оскільки тести не скомпільовані як частина механізму за замовчуванням:

scons tests=yes

Після завершення збірки запустіть тести за допомогою параметра командного рядка --test:

./bin/<godot_binary> --test

Тестовий запуск можна налаштувати за допомогою різних параметрів командного рядка doctest. Щоб отримати повний список підтримуваних опцій, запустіть команду --test з опцією --help:

./bin/<godot_binary> --test --help

Будь-які інші параметри та аргументи після команди --test розглядаються як аргументи для doctest.

Примітка

Тести компілюються автоматично, якщо ви використовуєте параметр SCons dev_mode=yes. dev_mode=yes рекомендується, якщо ви плануєте зробити внесок у розробку двигуна, оскільки він автоматично розглядатиме попередження компіляції як помилки. Система безперервної інтеграції вийде з ладу, якщо будуть виявлені будь-які попередження компіляції, тому ви повинні прагнути виправити всі попередження перед тим, як відкривати запит на отримання.

Фільтрування тестів

За замовчуванням усі тести виконуються, якщо ви не вказуєте жодних додаткових аргументів після команди --test. Але якщо ви пишете нові тести або бажаєте побачити успішні висновки цих тестів для цілей налагодження, ви можете запустити цікаві тести з різними параметрами фільтрації, які надає doctest.

Синтаксис символів підстановки * підтримується для відповідності будь-якої кількості символів у наборах тестів, тестових прикладах і назвах вихідних файлів:

Параметри фільтра

Стенографія

Приклади

--test-suite

-ts

-ts="*[GDScript]*"

--test-case

-tc

-tc="*[String]*"

--source-file

-sf

-sf="*test_color*"

Наприклад, щоб запустити лише модульні тести String, запустіть:

./bin/<godot_binary> --test --test-case="*[String]*"

Успішний вихід тверджень можна ввімкнути за допомогою параметра --success (-s), і його можна поєднати з будь-якою комбінацією параметрів фільтрації вище, наприклад:

./bin/<godot_binary> --test --source-file="*test_color*" --success

Певні тести можна пропустити за допомогою відповідних параметрів -exclude. На даний момент деякі тести включають випадкові стрес-тести, виконання яких вимагає певного часу. Щоб пропустити такі тести, виконайте таку команду:

./bin/<godot_binary> --test --test-case-exclude="*[Stress]*"

Написання контрольних робіт

Test suites represent C++ implementation files which must include the TEST_FORCE_LINK() macro. Most test suites are located directly under tests/ directory.

All test files are prefixed with test_, and this is a naming convention which the Godot build system relies on to detect tests throughout the engine.

Ось мінімальний робочий набір тестів з одним написаним тестом:

#include "tests/test_macros.h"

TEST_FORCE_LINK(test_string)

namespace TestString {

TEST_CASE("[String] Hello World!") {
    String hello = "Hello World!";
    CHECK(hello == "Hello World!");
}

} // namespace TestString

Примітка

You can quickly generate new tests using the create_test.py script found in the tests/ directory. This script automatically creates a new test file with the required boilerplate code in the appropriate location. To view usage instructions, run the script with the -h flag.

Заголовок tests/test_macros.h інкапсулює все необхідне для написання модульних тестів C++ у Godot. Він включає макроси для підтвердження документа та ведення журналу, такі як CHECK, як показано вище, і, звичайно ж, визначення для написання самих тестових випадків.

Дивись також

tests/test_macros.h вихідний код для поточних реалізованих макросів і псевдонімів для них.

Тестові випадки створюються за допомогою макросу TEST_CASE. Кожен тестовий випадок повинен мати короткий опис, написаний у дужках, за бажанням, включаючи спеціальні теги, які дозволяють фільтрувати тести під час виконання, наприклад [String], [Stress] тощо.

Тестові випадки записуються у спеціальному просторі імен. Це не є обов’язковим, але дозволяє запобігти конфліктам іменування, коли інші статичні допоміжні функції написані для повторюваних процедур тестування, таких як заповнення загальних тестових даних для кожного тесту або написання параметризованих тестів.

Godot підтримує написання тестів для кожного модуля C++. Щоб отримати інструкції щодо написання модульних тестів, зверніться до Написання індивідуальних тестів.

Підвипадки

У ситуаціях, коли у вас є загальні налаштування для кількох тестів із лише невеликими варіаціями, підкейси можуть бути дуже корисними. Ось приклад:

TEST_CASE("[SceneTree][Node] Testing node operations with a very simple scene tree") {
    // ... common setup (e.g. creating a scene tree with a few nodes)
    SUBCASE("Move node to specific index") {
        // ... setup and checks for moving a node
    }
    SUBCASE("Remove node at specific index") {
        // ... setup and checks for removing a node
    }
}

Кожен SUBCASE змушує TEST_CASE виконуватися з самого початку. Підвипадки можна вкладати до будь-якої глибини, але рекомендується обмежити вкладення не більше одного рівня.

Твердження

Список усіх часто використовуваних тверджень, які використовуються в тестах Godot, відсортованих за ступенем тяжкості.

Твердження

Опис

REQUIRE

Перевірте, чи виконується умова. Відразу провалює весь тест, якщо умова не виконується.

REQUIRE_FALSE

Перевірте, чи умова не виконується. Не проходить весь тест негайно, якщо умова виконується.

CHECK

Перевірте, чи виконується умова. Позначає тестовий запуск як невдалий, але дозволяє виконувати інші твердження.

CHECK_FALSE

Перевірте, чи умова не виконується. Позначає тестовий запуск як невдалий, але дозволяє виконувати інші твердження.

WARN

Перевірте, чи виконується умова. Не проходить тест за жодних обставин, але реєструє попередження, якщо щось не відповідає дійсності.

WARN_FALSE

Перевірте, чи умова не виконується. Не проходить тест за жодних обставин, але реєструє попередження, якщо щось вірно.

Усі наведені вище твердження мають відповідні макроси *_MESSAGE, які дозволяють надрукувати необов’язкове повідомлення з обґрунтуванням того, що має статися.

Віддавайте перевагу використанню CHECK для тверджень, що не потребують пояснень, і CHECK_MESSAGE для більш складних, якщо ви вважаєте, що це заслуговує на краще пояснення.

Лісозаготівля

Тестовий результат обробляється самим doctest і взагалі не покладається на функцію друку або журналювання Godot, тому рекомендується використовувати спеціальні макроси, які дозволяють реєструвати тестовий вихід у форматі, написаному doctest.

Макро

Опис

MESSAGE

Друкує повідомлення.

FAIL_CHECK

Позначає тест як невдалий, але продовжує виконання. Можна загорнути в умови для складних перевірок.

FAIL

Одразу провалює тест. Можна загорнути в умови для складних перевірок.

Під час виконання можна вибрати різних репортерів. Наприклад, ось як вихідні дані можна перенаправити у файл XML:

./bin/<godot_binary> --test --source-file="*test_validate*" --success --reporters=xml --out=doctest.txt

Тестування шляхів відмов

Іноді не завжди можливо перевірити очікуваний результат. Зважаючи на філософію розробки Godot, яка полягає в тому, що двигун не повинен виходити з ладу та повинен плавно відновлюватися щоразу, коли виникає нефатальна помилка, важливо перевірити, чи ці шляхи відмов справді безпечні для виконання без збою двигуна.

Неочікувану поведінку можна перевірити так само, як і будь-що інше. Єдина проблема, яку це створює, полягає в тому, що друк помилок буде без потреби забруднювати результат тесту помилками, що надходять від самого механізму (навіть якщо кінцевий результат буде успішним).

Щоб полегшити цю проблему, використовуйте макроси ERR_PRINT_OFF і ERR_PRINT_ON безпосередньо в тестових випадках, щоб тимчасово вимкнути виведення помилок, що надходить від механізму, наприклад:

TEST_CASE("[Color] Constructor methods") {
    ERR_PRINT_OFF;
    Color html_invalid = Color::html("invalid");
    ERR_PRINT_ON; // Don't forget to re-enable!

    CHECK_MESSAGE(html_invalid.is_equal_approx(Color()),
        "Invalid HTML notation should result in a Color with the default values.");
}

Спеціальні теги в назвах тестових випадків

Ці теги можна додати до назви тестового випадку, щоб змінити або розширити тестове середовище:

Тег

Опис

[SceneTree]

Необхідний для тестів, які покладаються на дерево сцени з MessageQueue, щоб бути доступним. Він також увімкне сервер імітаційного відтворення та ThemeDB.

[Editor]

Як [SceneTree], але з додатковою доступною інфраструктурою, пов’язаною з редактором, як-от EditorSettings.

[Audio]

Ініціалізує AudioServer за допомогою фіктивного звукового драйвера.

[Navigation2D]

Створює сервер 2D-навігації за замовчуванням та робить його доступним для тестування.

[Navigation3D]

Створює сервер 3D-навігації за замовчуванням та робить його доступним для тестування.

Ви можете використовувати їх разом, щоб поєднати кілька розширень тестового середовища.

Тестування сигналів

Наступні макроси можна використовувати для перевірки сигналів:

Макрос

Опис

SIGNAL_WATCH(object, "signal_name")

Починає перегляд заданого сигналу на даному об'єкті.

SIGNAL_UNWATCH(object, "signal_name")

Припиняє перегляд зазначеного сигналу на даному об'єкті.

SIGNAL_CHECK("signal_name", Vector<Vector<Variant>>)

Перевіряє аргументи всіх випущених сигналів. Зовнішній вектор містить кожен запущений сигнал, тоді як внутрішній вектор містить список аргументів для цього сигналу. Важливим є порядок сигналів.

SIGNAL_CHECK_FALSE("signal_name")

Перевіряє, чи не було подано вказаний сигнал.

SIGNAL_DISCARD("signal_name")

Відкидає всі записи вказаного сигналу.

Нижче наведено приклад використання цих макросів:

//...
SUBCASE("[Timer] Timer process timeout signal must be emitted") {
    SIGNAL_WATCH(test_timer, SNAME("timeout"));
    test_timer->start(0.1);

    SceneTree::get_singleton()->process(0.2);

    Array signal_args;
    signal_args.push_back(Array());

    SIGNAL_CHECK(SNAME("timeout"), signal_args);

    SIGNAL_UNWATCH(test_timer, SNAME("timeout"));
}
//...

Інструменти тестування

Інструменти тестування — це розширені методи, які дозволяють запускати довільні процедури для полегшення процесу ручного тестування та налагодження внутрішніх елементів двигуна.

Ці інструменти можна запускати, вказавши назву інструменту після параметра командного рядка --test. Наприклад, модуль GDScript реалізує та реєструє кілька інструментів, які допомагають налагоджувати токенізатор, парсер і компілятор:

./bin/<godot_binary> --test gdscript-tokenizer test.gd
./bin/<godot_binary> --test gdscript-parser test.gd
./bin/<godot_binary> --test gdscript-compiler test.gd

Якщо будь-який такий інструмент виявлено, решта модульних тестів пропускаються.

Інструменти тестування можна зареєструвати будь-де в системі, оскільки механізм реєстрації дуже схожий на той, який надає doctest під час реєстрації тестових випадків за допомогою техніки динамічної ініціалізації, але зазвичай їх можна зареєструвати у відповідних джерелах register_types.cpp (на модуль або ядро). .

Ось приклад того, як GDScript реєструє інструменти тестування в modules/gdscript/register_types.cpp:

#ifdef TESTS_ENABLED
void test_tokenizer() {
    TestGDScript::test(TestGDScript::TestType::TEST_TOKENIZER);
}

void test_parser() {
    TestGDScript::test(TestGDScript::TestType::TEST_PARSER);
}

void test_compiler() {
    TestGDScript::test(TestGDScript::TestType::TEST_COMPILER);
}

REGISTER_TEST_COMMAND("gdscript-tokenizer", &test_tokenizer);
REGISTER_TEST_COMMAND("gdscript-parser", &test_parser);
REGISTER_TEST_COMMAND("gdscript-compiler", &test_compiler);
#endif

Настроюваний аналіз командного рядка може виконуватися самим інструментом тестування за допомогою методу ОС get_cmdline_args.

Інтеграційні тести для GDScript

Godot використовує doctest для запобігання регресії в GDScript під час розробки. Існує кілька типів тестових сценаріїв, які можна написати:

  • тести на очікувані помилки;

  • тести на попередження;

  • тестує можливості.

Отже, процес написання інтеграційних тестів для GDScript такий:

  1. Виберіть тип тестового сценарію, який ви хочете написати, і створіть новий файл GDScript у каталозі modules/gdscript/tests/scripts у відповідному підкаталозі.

  2. Напишіть код GDScript. Тестовий скрипт повинен мати функцію під назвою test(), яка не приймає аргументів. Така функція буде викликана тестувальником. Тест не повинен мати жодних залежностей, якщо він також не є частиною тесту. Глобальні класи (з використанням class_name) реєструються перед запуском бігуна, тому вони повинні працювати, якщо потрібно.

    Ось приклад тестового сценарію:

    func test():
        if true # Missing colon here.
            print("true")
    
  3. Змініть каталог на кореневий каталог вихідного сховища Godot.

    cd godot
    
  4. Згенеруйте файли *.out, щоб оновити очікувані результати з виведення:

    bin/<godot_binary> --gdscript-generate-tests modules/gdscript/tests/scripts
    

Ви можете додати опцію --print-filenames, щоб бачити назви файлів під час генерації їхніх тестових результатів. Якщо ви працюєте над новою функцією, яка спричиняє серйозні збої, ви можете скористатися цією опцією, щоб швидко знайти, який тестовий файл спричинив збій, і налагодити з нього.

  1. Запустіть тести GDScript за допомогою:

    ./bin/<godot_binary> --test --test-suite="*GDScript*"
    

Це також приймає опцію --print-filenames (див. вище).

Якщо помилки не друкуються і все йде добре, готово!

Попередження

Перш ніж надсилати запит на отримання, переконайтеся, що вихідні дані мають очікувані значення. Якщо --gdscript-generate-tests створює файли *.out, які не пов’язані з щойно доданими тестами, вам слід повернути ці файли назад і зафіксувати лише файли *.out для нових тестів.

Примітка

Програма тестування GDScript призначена для тестування реалізації GDScript, а не для тестування користувальницьких сценаріїв або тестування механізму за допомогою сценаріїв. Ми рекомендуємо писати нові тести для вже вирішених «проблем, пов’язаних із GDScript, на GitHub, або написання тестів для поточних працюючих функцій.

Примітка

Якщо ваш тест вимагає, щоб у файлі сценарію не було функції test(), ви можете вимкнути розділ часу виконання тесту, назвавши файл сценарію так, щоб він відповідав шаблону *.notest.gd. Наприклад, "test_empty_file.notest.gd".