Skip to content

Test Attributes

ODD Games edited this page Oct 14, 2026 · 3 revisions

Test Attributes

Bugpunch tests are standard NUnit tests, so you organise and annotate them with NUnit's own attributes. The SDK adds one attribute of its own, [BugpunchTestReset], for resetting game state between automated runs.

Earlier versions documented a [UITest(Feature, Story, Severity, …)] attribute and a TestSeverity enum. Neither ships in the current package; use the NUnit equivalents below.

NUnit Metadata

Need NUnit attribute
Feature area / filtering [Category("Authentication")]
Description of what is validated [Description("Valid credentials reach the main menu")]
Arbitrary key/value metadata (story, owner, severity) [Property("Story", "User can log in")]
Maximum duration [Timeout(180000)] (milliseconds)
Run order within a fixture [Order(1)]
Skip a test [Ignore("Reason")]

UnityEngine also defines a PropertyAttribute, so in a file that imports both namespaces add an alias for NUnit's:

using System.Threading.Tasks;
using NUnit.Framework;
using UnityEngine;
using Property = NUnit.Framework.PropertyAttribute;
using static ODDGames.BugpunchSdk.ActionExecutor;

[TestFixture]
[Category("Authentication")]
[Property("Owner", "qa-team")]
public class LoginTests
{
    [Test]
    [Description("Valid credentials reach the main menu")]
    [Property("Story", "User can log in")]
    [Property("Severity", "Critical")]
    [Timeout(180000)]
    public async Task Login_WithValidCredentials_ShowsMainMenu()
    {
        await TextInput(Adjacent("Username:"), "testuser");
        await TextInput(Adjacent("Password:"), "password");
        await Click(Text("Login"));
        await WaitFor(Name("MainMenu"));
    }
}

Filtering by Category

The Test Runner window's category dropdown filters by [Category]. From the command line, pass -testCategory:

Unity -batchmode -projectPath . -runTests -testPlatform PlayMode -testCategory "Authentication;Shopping"

Group tests by feature with one category per fixture, and add extra categories ("slow", "nightly", "requires-server") for scheduling:

using NUnit.Framework;

[TestFixture, Category("Performance"), Category("nightly")]
public class LongRunningTests { }

[TestFixture, Category("Integration"), Category("requires-server")]
public class NetworkTests { }

Reading Metadata at Runtime

NUnit exposes the current test's properties through TestContext:

var props = TestContext.CurrentContext.Test.Properties;
var story = props.Get("Story") as string;
var categories = props["Category"];
Debug.Log($"Running '{TestContext.CurrentContext.Test.Name}' story={story}");

[BugpunchTestReset]

Mark a static method in your game code with [BugpunchTestReset] to have it called when an automated run asks the game to reset its state — for example clearing saves, scores or singletons:

using UnityEngine;
using ODDGames.BugpunchSdk;

public static class GameTestHooks
{
    [BugpunchTestReset]
    public static void ResetForTest()
    {
        PlayerPrefs.DeleteAll();
    }
}

The SDK invokes every [BugpunchTestReset] method it finds in the loaded assemblies when a connected Remote IDE / test runner sends POST /test/reset to the device. It is not called automatically by NUnit; in a local NUnit test, call your reset method from [SetUp] directly.

Best Practices

Consistent Category Names

using NUnit.Framework;

// Good - one spelling everywhere
[TestFixture, Category("User Profile")] public class ProfileEditTests { }
[TestFixture, Category("User Profile")] public class ProfilePictureTests { }

// Avoid - three spellings of the same area make filtering unreliable
[TestFixture, Category("UserProfile")] public class ProfileSettingsTests { }
[TestFixture, Category("Profile")] public class ProfileStatsTests { }

Descriptive Test Names

The method name is what the Test Runner shows first — make it describe the user goal:

// Good - describes the scenario and outcome
[Test] public async Task Profile_UploadPicture_ShowsNewAvatar() { }

// Avoid - says nothing about what failed
[Test] public async Task ProfileTest1() { }

Related Pages

Clone this wiki locally