AGENT SKILL

gds-test-automate

Generate automated game tests for gameplay systems. Use when the user says "automate tests" or "generate tests"

使用场景测试与质量
License 许可
MIT
最近更新
2026-07-19
Stars
★ 1
Forks
0

01 前言 Front Matter

name
gds-test-automate
description
Generate automated game tests for gameplay systems. Use when the user says "automate tests" or "generate tests"

02 资源文件 2

03 SKILL.md 正文

Game Test Automation Workflow

Goal: Generate automated test code for game projects based on test design scenarios or by analyzing existing game code. Creates engine-appropriate tests for Unity, Unreal, or Godot with proper patterns, fixtures, and cleanup.

Your Role: You are a senior game QA engineer and test automation specialist. Work autonomously to analyze the game codebase, detect the engine in use, and generate well-structured unit, integration, and smoke tests. You bring structured testing knowledge and engine-specific patterns, while the user brings domain context about the game's systems.


Conventions

  • Bare paths (e.g. template.md) resolve from the skill root.
  • {skill-root} resolves to this skill's installed directory (where customize.toml lives).
  • {project-root}-prefixed paths resolve from the project working directory.
  • {skill-name} resolves to the skill directory's basename.

On Activation

Step 1: Resolve the Workflow Block

Run: python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow

If the script fails, resolve the workflow block yourself by reading these three files in base → team → user order and applying the same structural merge rules as the resolver:

  1. {skill-root}/customize.toml — defaults
  2. {project-root}/_bmad/custom/{skill-name}.toml — team overrides
  3. {project-root}/_bmad/custom/{skill-name}.user.toml — personal overrides

Any missing file is skipped. Scalars override, tables deep-merge, arrays of tables keyed by code or id replace matching entries and append new entries, and all other arrays append.

Step 2: Execute Prepend Steps

Execute each entry in {workflow.activation_steps_prepend} in order before proceeding.

Step 3: Load Persistent Facts

Treat every entry in {workflow.persistent_facts} as foundational context you carry for the rest of the workflow run. Entries prefixed file: are paths or globs under {project-root} — load the referenced contents as facts. All other entries are facts verbatim.

Step 4: Load Config

Load config from {project-root}/_bmad/gds/config.yaml and resolve:

  • user_name
  • communication_language
  • output_folder
  • date as the system-generated current datetime

Step 5: Greet the User

Greet {user_name}, speaking in {communication_language}.

Step 6: Execute Append Steps

Execute each entry in {workflow.activation_steps_append} in order.

Activation is complete. Begin the workflow below.

WORKFLOW ARCHITECTURE

This uses an inline workflow pattern for autonomous execution:

  • Steps execute sequentially with full autonomy
  • Engine detection drives all subsequent decisions
  • All test files are written to disk as they are generated
  • A final summary report is produced at completion

Preflight Requirements

Before proceeding, verify:

  • Test framework already initialized (run framework workflow first)
  • Test scenarios defined (from test-design workflow or ad-hoc)
  • Game code accessible for analysis

If any preflight requirement is not met, HALT and guide the user.

Paths

  • installed_path = {skill_root}
  • validation = {installed_path}/checklist.md
  • test_dir = {project-root}/tests
  • source_dir = {project-root}/src

Variables

  • coverage_target = critical-paths (options: critical-paths, comprehensive, selective)
  • game_engine = auto (options: auto, unity, unreal, godot)
  • default_output_file = {output_folder}/automation-summary.md

Knowledge Fragments

Load the engine-specific knowledge fragment after engine detection in Step 1:

  • Unity: {installed_path}/knowledge/unity-testing.md
  • Unreal: {installed_path}/knowledge/unreal-testing.md
  • Godot: {installed_path}/knowledge/godot-testing.md
  • E2E patterns: {installed_path}/knowledge/e2e-testing.md

EXECUTION

<workflow>

<step n="1" goal="Analyze Codebase"> <action>Detect Game Engine by checking for engine-specific project files: - Unity: Assets/, ProjectSettings/, *.unity scenes - Unreal: *.uproject, Source/, Config/DefaultEngine.ini - Godot: project.godot, *.tscn, *.gd files </action> <action>Load the appropriate engine-specific knowledge fragment</action> <action>Identify testable systems in the codebase: - Pure logic classes (calculators, managers) - State machines (AI, gameplay) - Data structures (inventory, save data) </action> <action>Locate existing tests: - Find test directory structure - Identify test patterns already in use - Check for test helpers/fixtures </action> </step>

<step n="2" goal="Generate Unit Tests"> <action>For each identified testable system, generate a test file using the appropriate engine template below</action>

<!-- Unity (C#) --> <check if="engine == 'unity'"> <action>Generate NUnit test fixtures following this pattern:

using NUnit.Framework;

[TestFixture]
public class {ClassName}Tests
{
    private {ClassName} _sut;

    [SetUp]
    public void Setup()
    {
        _sut = new {ClassName}();
    }

    [Test]
    public void {MethodName}_When{Condition}_Should{Expectation}()
    {
        // Arrange
        {setup_code}
        // Act
        var result = _sut.{MethodName}({parameters});
        // Assert
        Assert.AreEqual({expected}, result);
    }

    [TestCase({input1}, {expected1})]
    [TestCase({input2}, {expected2})]
    public void {MethodName}_Parameterized({inputType} input, {outputType} expected)
    {
        var result = _sut.{MethodName}(input);
        Assert.AreEqual(expected, result);
    }
}
</action>

</check>

<!-- Unreal (C++) --> <check if="engine == 'unreal'"> <action>Generate Automation Test macros following this pattern:

#include "Misc/AutomationTest.h"

IMPLEMENT_SIMPLE_AUTOMATION_TEST(
    F{ClassName}{MethodName}Test,
    "{ProjectName}.{Category}.{TestName}",
    EAutomationTestFlags::ApplicationContextMask |
    EAutomationTestFlags::ProductFilter
)

bool F{ClassName}{MethodName}Test::RunTest(const FString& Parameters)
{
    // Arrange
    {setup_code}
    // Act
    auto Result = {ClassName}::{MethodName}({parameters});
    // Assert
    TestEqual("{assertion_message}", Result, {expected});
    return true;
}
</action>

</check>

<!-- Godot (GDScript) --> <check if="engine == 'godot'"> <action>Generate GUT test files following this pattern:

extends GutTest

var _sut: {ClassName}

func before_each():
    _sut = {ClassName}.new()

func after_each():
    _sut.free()

func test_{method_name}_when_{condition}_should_{expectation}():
    # Arrange
    {setup_code}
    # Act
    var result = \_sut.{method_name}({parameters})
    # Assert
    assert_eq(result, {expected}, "{assertion_message}")

func test_{method_name}_parameterized():
    var test_cases = [
        {"input": {input1}, "expected": {expected1}},
        {"input": {input2}, "expected": {expected2}}
    ]
    for tc in test_cases:
        var result = \_sut.{method_name}(tc.input)
        assert_eq(result, tc.expected)
</action>

</check>

<action>Write each generated unit test file to the appropriate location under {test_dir}/unit/</action> </step>

<step n="3" goal="Generate Integration Tests"> <action>Generate scene/level integration tests using the appropriate engine template</action>

<check if="engine == 'unity'"> <action>Generate Unity Play Mode integration tests:

[UnityTest]
public IEnumerator {SceneName}_Loads_WithoutErrors()
{
    SceneManager.LoadScene("{scene_name}");
    yield return new WaitForSeconds(2f);
    var errors = GameObject.FindObjectsOfType<ErrorHandler>()
        .Where(e => e.HasErrors);
    Assert.IsEmpty(errors, "Scene should load without errors");
}
</action>

</check>

<check if="engine == 'unreal'"> <action>Generate Unreal Functional Test actors:

void A{TestName}::StartTest()
{
    Super::StartTest();
    {setup}
    if ({condition})
        FinishTest(EFunctionalTestResult::Succeeded, "{message}");
    else
        FinishTest(EFunctionalTestResult::Failed, "{failure_message}");
}
</action>

</check>

<check if="engine == 'godot'"> <action>Generate Godot integration tests:

func test_{feature}_integration():
    var scene = load("res://scenes/{scene}.tscn").instantiate()
    add_child(scene)
    await get_tree().process_frame
    {test_code}
    scene.queue_free()
</action>

</check>

<action>Write each generated integration test file to {test_dir}/integration/</action> </step>

<step n="3.5" goal="Generate E2E Infrastructure"> <action>Before generating E2E tests, scaffold the required infrastructure components: 1. Test Fixture Base Class — scene loading/unloading, game ready state waiting, common service access, cleanup guarantees 2. Scenario Builder — fluent API for game state configuration, domain-specific methods, yields for state propagation 3. Input Simulator — click/drag abstractions, button press simulation, keyboard input queuing 4. Async Assertions — WaitUntil with timeout and message, WaitForEvent for event-driven flows, WaitForState for state machine transitions </action> <action>Generate the GameE2ETestFixture base class using this template:

public abstract class GameE2ETestFixture
{
    protected {GameStateClass} GameState;
    protected {InputSimulatorClass} Input;
    protected {ScenarioBuilderClass} Scenario;

    [UnitySetUp]
    public IEnumerator BaseSetUp()
    {
        yield return LoadScene("{main_scene}");
        GameState = Object.FindFirstObjectByType<{GameStateClass}>();
        Input = new {InputSimulatorClass}();
        Scenario = new {ScenarioBuilderClass}(GameState);
        yield return WaitForReady();
    }
}

</action> <action>Write infrastructure files to {test_dir}/e2e/infrastructure/ or the engine-appropriate equivalent</action> <action>After scaffolding infrastructure, proceed to generate actual E2E tests</action> </step>

<step n="4" goal="Generate Smoke Tests"> <action>Create critical path tests that run on every build, covering: 1. Game launches without crash 2. Main menu is navigable 3. New game starts successfully 4. Core gameplay loop executes 5. Save/load works </action> <action>Generate engine-appropriate smoke tests, for example (Unity):

[UnityTest, Timeout(60000)]
public IEnumerator Smoke_NewGame_StartsSuccessfully()
{
    SceneManager.LoadScene("MainMenu");
    yield return new WaitForSeconds(2f);
    var newGameButton = GameObject.Find("NewGameButton");
    newGameButton.GetComponent<Button>().onClick.Invoke();
    yield return new WaitForSeconds(5f);
    var player = GameObject.FindWithTag("Player");
    Assert.IsNotNull(player, "Player should exist after new game");
}

</action> <action>Write smoke tests to {test_dir}/smoke/</action>

<!-- Anti-patterns to actively avoid --> <action>Ensure generated tests do NOT: - Test engine functionality (not game logic) - Use hard-coded waits as primary sync (use signals/events) - Depend on execution order - Lack cleanup in teardown </action> </step>

<step n="5" goal="Generate Test Report"> <action>After all test files have been written, create an automation summary at {default_output_file} using this structure:

## Automation Summary

**Engine**: {Unity | Unreal | Godot}
**Tests Generated**: {count}
**Date**: {date}

### Test Distribution

| Type        | Count | Coverage      |
| ----------- | ----- | ------------- |
| Unit Tests  | {n}   | {systems}     |
| Integration | {n}   | {features}    |
| Smoke Tests | {n}   | Critical path |

### Files Created

- `tests/unit/{file1}.{ext}`
- `tests/integration/{file2}.{ext}`
- `tests/smoke/{file3}.{ext}`

### Next Steps

1. Review generated tests
2. Fill in test-specific logic where placeholders remain
3. Run tests to verify they pass
4. Add to CI pipeline

</action> <action>Load and apply {validation} checklist to verify all deliverables are complete</action> <action>Present the automation summary to the user</action> <action>Run: python3 {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --key workflow.on_complete — if the resolved value is non-empty, follow it as the final terminal instruction before exiting.</action> </step>

</workflow>

04 同仓库更多 Skills