- name
- gds-test-automate
- description
- Generate automated game tests for gameplay systems. Use when the user says "automate tests" or "generate tests"
# 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}` is the nearest folder containing `_bmad/`, starting at the project working directory and moving up through its parents.
- `{skill-name}` resolves to the skill directory's basename.
## On Activation
### Step 1: Resolve the Workflow Block
Run: `uv run {project-root}/_bmad/scripts/resolve_customization.py --skill {skill-root} --project-root {project-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. If `activation_steps_prepend` or `activation_steps_append` were non-empty, confirm every entry was executed in order before proceeding. Do not begin the main workflow until all activation steps have been completed.
## 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:
```csharp
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:
```cpp
#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:
```gdscript
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:
```csharp
[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:
```cpp
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:
```gdscript
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:
```csharp
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):
```csharp
[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:
```markdown
## 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>
GitHub에서 보기