GoDotTest

repository·main·Indexed 40 days ago

https://github.com/chickensoft-games/godottest

A C# test runner for Godot that enables unit and integration tests directly within the engine. It supports code coverage via coverlet, standard debugging workflows in VS Code and Visual Studio, and provides lifecycle attributes like [Setup], [Cleanup], and [Test] for managing test suites.

Tokens
3.5K
Snippets
8
Records
11
Agent score
50%

What's inside GoDotTest

  1. How GoDotTest works

    main

    GoDotTest provides a deterministic, C#-first approach to testing in Godot using the following components:

    • TestProvider: Uses reflection to find all test suites (classes extending TestClass) in the current assembly. It constructs a TestEnvironment based on Godot command-line arguments.
    • TestExecutor: Runs methods marked with the [Test] attribute in the order they are declared in a TestClass.
    • TestReporter: Responds to test events to display output.

    Key Behaviors:

    • Async Support: GoDotTest awaits any async Task test methods.
    • No Parallelism: Tests do not run in parallel to prevent race conditions in visual or integration-style tests.
    • Customization: You can use GoTest.cs as a starting point if you need to customize how tests are loaded and run.
  2. Recommended tools for Mocking and Integration Testing

    main

    GoDotTest is a test runner and execution system. For other testing needs, the following tools are recommended:

    • Mocking: Use [LightMock.Generator] for compile-time mock generation. For a Moq-like API, use the [LightMoq] adapter.
    • Integration Testing: Use [GodotTestDriver] to simulate input, wait for frames, and interact with UI elements.
  3. Configure Visual Studio for debugging GoDotTest

    main

    To debug tests in Visual Studio, create a launchSettings.json file in the Properties subdirectory of your test project. Ensure the %GODOT% environment variable is set or replace it with the direct path to the executable.

    {
      "profiles": {
        "Debug Tests": {
          "commandName": "Executable",
          "executablePath": "%GODOT%",
          "commandLineArgs": "--run-tests --listen-trace --quit-on-finish",
          "workingDirectory": "."
        }
      }
    }
  4. Integrate GoDotTest into a Game workflow

    main

    For games, you should integrate GoDotTest into your main entry point so it can detect command-line arguments and decide whether to run tests or start the game.

    1. Exclude tests from release builds: Add this to your .csproj to prevent test files from being included in exported release builds:

    <PropertyGroup>
      <DefaultItemExcludes Condition="$(Configuration)' == 'ExportRelease'">
        $(DefaultItemExcludes);test/**/*
      </DefaultItemExcludes>
    </PropertyGroup>

    2. Implement the Main entry point logic: In your main scene (e.g., Main.tscn), use TestEnvironment.From(OS.GetCmdlineArgs()) to check if tests should run. If they should, call GoTest.RunTests. Otherwise, switch to your actual game scene (e.g., Game.tscn).

    namespace YourGame;
    
    using Godot;
    
    #if DEBUG
    using System.Reflection;
    using GoDotTest;
    #endif
    
    public partial class Main : Node2D
    {
    #if DEBUG
      public TestEnvironment Environment = default!;
    #endif
    
      public override void _Ready()
      {
    #if DEBUG
        // If this is a debug build, use GoDotTest to examine the
        // command line arguments and determine if we should run tests.
        Environment = TestEnvironment.From(OS.GetCmdlineArgs());
        if (Environment.ShouldRunTests)
        {
          CallDeferred("RunTests");
          return;
        }
    #endif
        // If we don't need to run tests, our game will start and immediately switch to the game scene.
        GetTree().ChangeSceneToFile("res://src/Game.tscn");
      }
    
    #if DEBUG
      private void RunTests()
        => _ = GoTest.RunTests(Assembly.GetExecutingAssembly(), this, Environment);
    #endif
    }
  5. Run tests from a Scene

    main

    To run tests within a Godot scene, create a test scene and attach a C# script that calls GoTest.RunTests. This is useful for standalone test scenes or package testing.

    using System.Reflection;
    using Godot;
    using GoDotTest;
    
    public partial class Tests : Node2D {
      public override async void _Ready()
        => await GoTest.RunTests(Assembly.GetExecutingAssembly(), this);
    }
  6. Install GoDotTest via NuGet

    main

    To use GoDotTest in your Godot C# project, add the Chickensoft.GoDotTest package to your .csproj file. Ensure you use the version that matches your Godot version (pre-release versions of Godot will have matching pre-release labels on the NuGet package).

    <ItemGroup>
      <PackageReference Include="Chickensoft.GoDotTest" Version="*VERSION*" />
    </ItemGroup>
  7. Collect code coverage with coverlet

    main

    If your project is configured to switch to the test scene when the --run-tests flag is passed, you can use the coverlet tool to run Godot and collect code coverage.

    Note that the --run-tests, --coverage, and --quit-on-finish flags are specific to GoDotTest. When the --coverage flag is supplied, GoDotTest will force-exit the process to ensure the exit code is set correctly for coverage tools, as Godot's SceneTree.Quit(int exitCode) does not set the process exit code. This may cause minor error messages during exit, which can be safely ignored.

    coverlet \
      "./.godot/mono/temp/bin/Debug" --verbosity detailed \
      --target $GODOT \
      --targetargs "--run-tests --coverage --quit-on-finish" \
      --format "opencover" \
      --output "./coverage/coverage.xml" \
      --exclude-by-file "**/test/**/*.cs" \
      --exclude-by-file "**/*Microsoft.NET.Test.Sdk.Program.cs" \
      --exclude-by-file "**/Godot.SourceGenerators/**/*.cs" \
      --exclude-assemblies-without-sources "missingall"
  8. Configure VS Code for debugging GoDotTest

    main

    To debug tests in VS Code, you must first set the GODOT environment variable to the path of your Godot executable (e.g., export GODOT="/Applications/Godot.app/Contents/MacOS/Godot" in your shell profile).

    Then, place the following tasks.json and launch.json files in a .vscode folder at your project root.

    // .vscode/tasks.json
    {
      "version": "2.0.0",
      "tasks": [
        {
          "label": "build",
          "command": "dotnet",
          "type": "process",
          "args": [
            "build",
            "--no-restore"
          ],
          "problemMatcher": "$msCompile",
          "presentation": {
            "echo": true,
            "reveal": "silent",
            "focus": false,
            "panel": "shared",
            "showReuseMessage": true,
            "clear": false
          }
        }
      ]
    }
    // .vscode/launch.json
    {
      "version": "0.2.0",
      "configurations": [
        {
          "name": "Play",
          "type": "coreclr",
          "request": "launch",
          "preLaunchTask": "build",
          "program": "${env:GODOT}",
          "args": [],
          "cwd": "${workspaceFolder}",
          "stopAtEntry": false,
        },
        {
          "name": "Debug Tests",
          "type": "coreclr",
          "request": "launch",
          "preLaunchTask": "build",
          "program": "${env:GODOT}",
          "args": [
            "--run-tests",
            "--quit-on-finish"
          ],
          "cwd": "${workspaceFolder}",
          "stopAtEntry": false,
        },
        {
          "name": "Debug Current Test",
          "type": "coreclr",
          "request": "launch",
          "preLaunchTask": "build",
          "program": "${env:GODOT}",
          "args": [
            "--run-tests=${fileBasenameNoExtension}",
            "--quit-on-finish"
          ],
          "cwd": "${workspaceFolder}",
          "stopAtEntry": false,
        }
      ]
    }
  9. Write unit tests using TestClass

    main

    To write a test suite, create a class that extends TestClass. The constructor must accept a Node (the test scene) and pass it to the base class. You can use lifecycle attributes to manage the state of your tests.

    Lifecycle Attributes:

    • [SetupAll]: Runs once before the first test in the suite.
    • [CleanupAll]: Runs once after all tests in the suite have finished.
    • [Setup]: Runs before each individual test.
    • [Cleanup]: Runs after each individual test.
    • [Test]: Marks a method as a test case.
    • [Failure]: Runs whenever any test in the same suite fails. Useful for taking screenshots or error management.
    using Chickensoft.GoDotTest;
    using Chickensoft.Log;
    using Godot;
    
    public class ExampleTest : TestClass
    {
      private readonly ILog _log = new Log(nameof(ExampleTest), new TraceWriter());
    
      public ExampleTest(Node testScene) : base(testScene) { }
    
      [SetupAll]
      public void SetupAll() => _log.Print("Setup everything");
    
      [Setup]
      public void Setup() => _log.Print("Setup");
    
      [Test]
      public void Test() => _log.Print("Test");
    
      [Cleanup]
      public void Cleanup() => _log.Print("Cleanup");
    
      [CleanupAll]
      public void CleanupAll() => _log.Print("Cleanup everything");
    
      [Failure]
      public void Failure() =>
        _log.Print("Runs whenever any of the tests in this suite fail.");
    }
  10. Troubleshoot missing test logs

    main

    If you cannot see your test logs in Godot, you may need to increase the network limits in your project settings.

    Go to Project Settings and increase the following values under Network Limits (you may need to enable Advanced Settings to see them):

    • Max Chars per Second
    • Max Queued Messages
    • Max Errors per Second
    • Max Warnings per Second
  11. GoDotTest Command Line Arguments

    main

    Use these flags when running Godot to control the GoDotTest execution behavior.

    --run-tests: Informs the game to run tests. Can be used to target specific suites or methods: `--run-tests=TestSuiteName` or `--run-tests=TestSuiteName.TestMethodName`.
    --listen-trace: Enables support for Visual Studio by adding a `DefaultTraceListener` to the global trace listeners. (Note: Using this in VS Code may result in doubled output).
    --quit-on-finish: Tells the test runner to exit the application immediately after finishing tests.
    --stop-on-error: Stops running tests as soon as the first error is encountered.
    --sequential: Skips subsequent test methods in a suite if an error occurs in a previous method. Ignored if `--stop-on-error` is used.
    --coverage: Required for Godot 4 when collecting coverage; allows GoDotTest to force-exit correctly.