fledware / GodotXUnit

MIT License
31 stars 5 forks source link

GodotXUnit

A plugin for executing Xunit tests within godot.

Examples

you can look at all the examples in the ./tests directory.

Run Test On Scene

[GodotFact(Scene = "res://test_scenes/SomeTestScene.tscn")]
public void IsOnCorrectScene()
{
    var scene = GDU.CurrentScene;
    Assert.Equal(typeof(SomeTestSceneRoot), scene?.GetType());
}

Run In Different Thread Contexts

[GodotFact]
public async void AllThreadContextInOne()
{
    Assert.False(Engine.IsInPhysicsFrame());

    await GDU.OnPhysicsProcessAwaiter;
    Assert.True(Engine.IsInPhysicsFrame());

    await GDU.OnProcessAwaiter;
    Assert.False(Engine.IsInPhysicsFrame());
}

Signal Waiting Or Timeout

I've added a signal waiter method with a timeout:

GDU.ToSignalWithTimeout(source, signal, timeoutMillis, throwOnTimeout)

This method helps simplify tests by allowing you to wait for signals but also ensuring the tests actually finish or different parts have timeouts. full example at ./tests/PhysicsCollisionTest.cs

    [GodotFact(Scene = "res://test_scenes/PhysicsCollisionTest.tscn")]
    public async void TestOhNoTooSlowOfFall()
    {
        var ball = (AVerySpecialBall) GDU.CurrentScene.FindChild("AVerySpecialBall");
        Assert.NotNull(ball);

        // it will throw a TimeoutException here because the gravity value is too low
        await GDU.ToSignalWithTimeout(ball, nameof(AVerySpecialBall.WeCollidedd), 2000);
        // it will never get here
        Assert.Equal(new Vector2(), ball.velocity);
    }

Installation

If there is a better way to do this, please let me know. I'd really like to make the installation of this easier and remove the manual steps.

Running Tests From A Sub Project

It's common in C# projects to separate unit and integration test into sub projects. This helps with package size and enforcing dependencies.

By default, GodotXUnit will attempt to run tests in the root project. If you have a subproject (with a csproj file at the root), you can run that by selecting the desired project in the target assembly option located at the top right of the GodotXUnit panel.

If you have a test project that is external from the godot project, GodotXUnit will not be able to automatically find the project. You will need to select 'Custom Location' in the drop down and put the absolute path to the dll in the 'Custom Assembly Path (dll)' label. Make sure to choose the dll that is in the bin directory, as xunit will need to load the dll dependencies, and they must live next to the assembly being discovered on.

Running Tests In CI

This project runs in github's CI to show working examples of how to run these tests in CI. The example can be found in .github/workflows/build.yaml.

Basically, you configure godot project with the override.cfg to tell GodotXUnitRunner what to run and where to put the results. Then, just run godot res://addons/GodotXUnit/runner/GodotTestRunnerScene.tscn.

Important Note About Unit Tests Vs Integration Tests

I don't want to get too philosophical.. But to make sure we're on the same page with definitions:

These two are important because both of these types of tests can be ran within GodotXUnit. But, if you are executing unit tests that don't require calling into an active godot API, then you can just use xunit to run the tests if you want. This also has the added benefit of integrating with IDEs.

For instance, I can run SubProjectForUnitTests in Rider. And one of the tests use the [GodotFact] attribute. This works because GodotXUnit will only attempt to call into godot APIs if you request it (like loading a scene for a test). This also means that other standard xunit tools should work just fine with the [GodotFact] attribute.

How It Works

When you click a run button, the first thing is the run args gets written to ./addons/GodotXUnit/_work/RunArgs.json. this allows the runner to know what needs to be ran.

then, the runner scene is started with this command:

OS.Execute(OS.GetExecutablePath(), new [] {"res://addons/GodotXUnit/runner/GodotTestRunnerScene.tscn"}, false);

then, events are listened to with file system events pulled by the editor. events are sent by the runner for tests starting/finishing, and the entire summary. this way we can update the editor while tests come in. events are passed at the ./addons/GodotXUnit/_work directory.

finally, the results will be written to res://TestSummary.json. you can change the location with the GodotXUnit/results_summary ProjectSetting.

Known Issues

Next steps

Thanks To

icons made by:

for great work: