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.

Unit-Tests

Godot Engine allows to write unit tests directly in C++. The engine integrates the doctest unit testing framework which gives ability to write test suites and test cases next to production code, but since the tests in Godot go through a different main entry point, the tests reside in a dedicated tests/ directory instead, which is located at the root of the engine source code.

Unterstützung von Plattformen und Targets

C++-Unit-Tests können auf Linux-, macOS- und Windows-Betriebssystemen ausgeführt werden.

Tests können nur mit aktivierten Editor-Tools durchgeführt werden, was bedeutet, dass Exportvorlagen derzeit nicht getestet werden können.

Ausführen von Tests

Bevor die Tests tatsächlich ausgeführt werden können, muss die Engine mit der Build-Option tests kompiliert werden (plus jede andere Build-Option, die Sie normalerweise verwenden), da die Tests nicht als Teil der Engine standardmäßig kompiliert werden:

scons tests=yes

Sobald der Build fertig ist, führen Sie die Tests mit der Kommandozeilenoption --test aus:

./bin/<godot_binary> --test

Der Testlauf kann mit den verschiedenen doctest-spezifischen Kommandozeilenoptionen konfiguriert werden. Um die vollständige Liste der unterstützten Optionen zu erhalten, führen Sie den Befehl --test mit der Option --help aus:

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

Alle anderen Optionen und Argumente nach dem Befehl --test werden als Argumente für doctest behandelt.

Bemerkung

Tests werden automatisch kompiliert, wenn Sie die dev_mode=yes SCons Option verwenden. dev_mode=yes wird empfohlen, wenn Sie vorhaben, zur Entwicklung der Engine beizutragen, da Compilerwarnungen automatisch als Fehler behandelt werden. Das Continuous-Integration-System wird fehlschlagen, wenn Compilerwarnungen entdeckt werden, daher sollten Sie sich bemühen, alle Warnungen zu beheben, bevor Sie einen Pull Request erstellen.

Filtern von Tests

Standardmäßig werden alle Tests ausgeführt, wenn Sie keine zusätzlichen Argumente nach dem --test Befehl angeben. Wenn Sie jedoch neue Tests schreiben oder die Ausgabe der erfolgreichen Assertions aus diesen Tests zu Debugging-Zwecken sehen möchten, können Sie die Tests, die Sie interessieren, mit den verschiedenen Filteroptionen von doctest ausführen.

Die Wildcard-Syntax * wird für die Suche nach einer beliebigen Anzahl von Zeichen in Testsuiten, Testfällen und Quellcode-Dateinamen unterstützt:

Filter-Optionen

Abkürzung

Beispiele

--test-suite

-ts

-ts="*[GDScript]*"

--test-case

-tc

-tc="*[String]*"

--source-file

-sf

-sf="*test_color*"

Um zum Beispiel nur die String-Unit-Tests auszuführen, führen Sie aus:

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

Die Ausgabe erfolgreicher Assertions kann mit der Option --success (-s) aktiviert werden und kann z.B. mit jeder Kombination der oben genannten Filteroptionen kombiniert werden:

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

Bestimmte Tests können mit den entsprechenden -exclude Optionen übersprungen werden. Zur Zeit beinhalten einige Tests zufällige Stresstests, die eine gewisse Zeit zur Ausführung benötigen. Um diese Art von Tests zu überspringen, führen Sie den folgenden Befehl aus:

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

Schreiben von Tests

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.

Hier ist eine minimale funktionierende Testsuite mit einem einzigen geschriebenen Test-Case:

#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

Bemerkung

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.

Der Header tests/test_macros.h kapselt alles, was zum Schreiben von C++-Unit-Tests in Godot benötigt wird. Er enthält Doctest-Assertion- und Logging-Makros wie CHECK (siehe oben) und natürlich die Definitionen zum Schreiben von Test-Cases selbst.

Siehe auch

tests/test_macros.h Quellcode für derzeit implementierte Makros und Aliase für diese Makros.

Test cases are created using TEST_CASE function-like macro. Each test case must have a brief description written in parentheses, optionally including custom tags which allow to filter the tests at runtime, such as [String], [Stress] etc.

Test-Cases werden in einem eigenen Namespace geschrieben. Dies ist nicht erforderlich, ermöglicht es aber, Namenskollisionen zu vermeiden, wenn andere statische Hilfsfunktionen geschrieben werden, um sich wiederholende Testverfahren unterzubringen, wie z.B. das Auffüllen gemeinsamer Testdaten für jeden Test oder das Schreiben parametrisierter Tests.

Godot unterstützt das Schreiben von Tests pro C++-Modul. Anweisungen zum Schreiben von Modultests finden Sie in Schreiben von benutzerdefinierten Unit-Tests.

Subcases

In situations where you have a common setup for several test cases with only slight variations, subcases can be very helpful. Here's an example:

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
    }
}

Each SUBCASE causes the TEST_CASE to be executed from the beginning. Subcases can be nested to an arbitrary depth, but it is advised to limit nesting to no more than one level deep.

Assertions

Eine Liste aller häufig verwendeten Assertions, die in den Godot-Tests verwendet werden, sortiert nach Schweregrad.

Assertion

Beschreibung

REQUIRE

Testet, ob die Bedingung erfüllt ist. Der gesamte Test schlägt sofort fehl, wenn die Bedingung nicht zutrifft.

REQUIRE_FALSE

Testet, wenn die Bedingung nicht erfüllt ist. Der gesamten Test schlägt sofort fehl, wenn die Bedingung erfüllt ist.

CHECK

Testet, ob die Bedingung erfüllt ist. Markiert den Testlauf als fehlgeschlagen, erlaubt aber die Ausführung anderer Assertions.

CHECK_FALSE

Testet, ob die Bedingung nicht erfüllt ist. Markiert den Testlauf als fehlgeschlagen, erlaubt aber die Ausführung anderer Assertions.

WARN

Testet, ob die Bedingung erfüllt ist. Lässt den Test unter keinen Umständen fehlschlagen, gibt aber eine Warnung aus, etwas nicht "true" ergibt.

WARN_FALSE

Testet, ob die Bedingung nicht zutrifft. Lässt den Test unter keinen Umständen fehlschlagen, gibt aber eine Warnung aus, wenn etwas "true" ergibt.

Alle oben genannten Assertions haben entsprechende *_MESSAGE-Makros, die es erlauben, eine optionale Nachricht mit der Begründung auszugeben, was erwartet wird.

Bevorzugen Sie CHECK für selbsterklärende Assertions und CHECK_MESSAGE für komplexere, wenn Sie denken, dass diese eine bessere Erklärung verdienen.

Logging

Die Testausgabe wird von doctest selbst gehandhabt und verlässt sich nicht auf die Print- oder Logging-Funktionalität von Godot. Es wird daher empfohlen, spezielle Makros zu verwenden, die es ermöglichen, die Testausgabe in einem von doctest geschriebenen Format zu loggen.

Makro

Beschreibung

MESSAGE

Gibt eine Nachricht aus.

FAIL_CHECK

Markiert den Test als fehlgeschlagen, setzt aber die Ausführung fort. Kann für komplexe Prüfungen in Conditionals verpackt werden.

FAIL

Der Test schlägt sofort fehl. Kann für komplexe Prüfungen in Conditionals verpackt werden.

Different reporters can be chosen at runtime. For instance, here's how the output can be redirected to an XML file:

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

Testen von Fehlerpfaden

Manchmal ist es nicht möglich, auf ein erwartetes Ergebnis zu testen. Da die Godot-Entwicklungsphilosophie vorsieht, dass die Engine nicht abstürzen und sich bei Auftreten eines nicht-kritischen Fehlers ordnungsgemäß erholen soll, ist es wichtig zu prüfen, ob diese Fehlerpfade tatsächlich sicher ausgeführt werden können, ohne dass die Engine abstürzt.

Unerwartetes Verhalten kann auf die gleiche Weise getestet werden wie alles andere. Das einzige Problem, das dadurch entsteht, ist, dass der Fehlertext die Testausgabe unnötig mit Fehlern vermüllt, die von der Engine selbst stammen (selbst wenn das Endergebnis erfolgreich ist).

Um dieses Problem zu umgehen, können Sie die Makros ERR_PRINT_OFF und ERR_PRINT_ON direkt in den Testfällen verwenden, um z.B. die Fehlerausgabe der Engine vorübergehend zu deaktivieren:

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.");
}

Special tags in test case names

These tags can be added to the test case name to modify or extend the test environment:

Tag

Beschreibung

[SceneTree]

Required for test cases that rely on a scene tree with MessageQueue to be available. It also enables a mock rendering server and ThemeDB.

[Editor]

Like [SceneTree], but with additional editor-related infrastructure available, such as EditorSettings.

[Audio]

Initializes the AudioServer using a mock audio driver.

[Navigation2D]

Creates the default 2D navigation server and makes it available for testing.

[Navigation3D]

Creates the default 3D navigation server and makes it available for testing.

You can use them together to combine multiple test environment extensions.

Testing signals

The following macros can be use to test signals:

Macro

Beschreibung

SIGNAL_WATCH(object, "signal_name")

Starts watching the specified signal on the given object.

SIGNAL_UNWATCH(object, "signal_name")

Stops watching the specified signal on the given object.

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

Checks the arguments of all fired signals. The outer vector contains each fired signal, while the inner vector contains the list of arguments for that signal. The order of signals is significant.

SIGNAL_CHECK_FALSE("signal_name")

Checks if the specified signal was not fired.

SIGNAL_DISCARD("signal_name")

Discards all records of the specified signal.

Below is an example demonstrating the use of these macros:

//...
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-Tools

Test-Tools sind fortgeschrittene Methoden, die es Ihnen ermöglichen, beliebige Prozeduren auszuführen, um den Prozess des manuellen Testens und des Debuggens der Engine-Interna zu erleichtern.

Diese Tools können durch die Angabe des Namens eines Tools nach der --test Kommandozeilenoption gestartet werden. Zum Beispiel implementiert und registriert das GDScript-Modul mehrere Tools, die bei der Fehlersuche im Tokenizer, Parser und Compiler helfen:

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

Wird ein solches Tool entdeckt, werden die restlichen Unit-Tests übersprungen.

Test-Tools können überall in der Engine registriert werden, da der Registrierungsmechanismus dem von doctest ähnelt, wenn Testfälle mit dynamischer Initialisierungstechnik registriert werden, aber normalerweise können diese in den entsprechenden register_types.cpp-Quellen (pro Modul oder Kern) registriert werden.

Hier ist ein Beispiel dafür, wie GDScript Test-Tools in modules/gdscript/register_types.cpp registriert:

#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

Das benutzerdefinierte Parsen der Kommandozeile kann von einem Test-Tool selbst mit Hilfe der Methode OS get_cmdline_args durchgeführt werden.

Integrationstests für GDScript

Godot verwendet doctest, um Regressions-Bugs in GDScript während der Entwicklung zu verhindern. Es gibt mehrere Arten von Testskripten, die geschrieben werden können:

  • Tests für erwartete Fehler;

  • Tests für Warnungen;

  • Tests für Performancemerkmale.

Der Prozess der Erstellung von Integrationstests für GDScript sieht daher wie folgt aus:

  1. Wählen Sie einen Typ eines Testskripts, das Sie schreiben möchten, und erstellen Sie eine neue GDScript-Datei im Verzeichnis modules/gdscript/tests/scripts im entsprechenden Unterverzeichnis.

  2. Schreiben Sie GDScript-Code. Das Testskript muss eine Funktion namens test() enthalten, die keine Argumente benötigt. Diese Funktion wird vom Test-Runner aufgerufen. Der Test sollte keine Abhängigkeiten haben, es sei denn, sie sind auch Teil des Tests. Globale Klassen (mit class_name) werden vor dem Start des Test-Runners registriert, so dass diese bei Bedarf funktionieren sollten.

    Hier ist ein Beispiel für ein Testskript:

    func test():
        if true # Missing colon here.
            print("true")
    
  3. Wechseln Sie in das Stammverzeichnis des Godot-Quellcode-Repositorys.

    cd godot
    
  4. Erzeugen Sie *.out-Dateien, um die erwarteten Ergebnisse der Ausgabe zu aktualisieren:

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

Sie können die Option --print-filenames hinzufügen, um die Dateinamen zu sehen, während die Testausgaben erzeugt werden. Wenn Sie an einem neuen Feature arbeiten, die schwere Abstürze verursacht, können Sie diese Option verwenden, um schnell herauszufinden, welche Testdatei den Absturz verursacht, und von dort aus debuggen.

  1. Führen Sie GDScript-Tests aus mit:

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

Dies akzeptiert auch die Option --print-filenames (siehe oben).

Wenn keine Fehler ausgegeben werden und alles gut geht, sind Sie fertig!

Warnung

Vergewissern Sie sich, dass die Ausgabe die erwarteten Werte hat, bevor Sie einen Pull Request einreichen. Wenn --gdscript-generate-tests *.out-Dateien erzeugt, die nichts mit den neu hinzugefügten Tests zu tun haben, sollten Sie diese Dateien reverten und nur *.out Dateien für neue Tests committen.

Bemerkung

Der GDScript-Testrunner ist zum Testen der GDScript-Implementierung gedacht, nicht zum Testen von Benutzerskripten oder zum Testen der Engine mithilfe von Skripten. Wir empfehlen, neue Tests für bereits gelöste Issues im Zusammenhang mit GDScript auf GitHub zu schreiben, oder Tests für derzeit in Arbeit befindliche Features zu schreiben.

Bemerkung

Wenn Ihr Test Case erfordert, dass innerhalb der Skriptdatei keine test() Funktion vorhanden ist, können Sie den Laufzeitabschnitt des Tests deaktivieren, indem Sie die Skriptdatei so benennen, dass sie dem Muster *.notest.gd entspricht. Zum Beispiel: "test_empty_file.notest.gd".