test-setup
SkillDev toolsScaffold the test framework and CI/CD pipeline. Supports game engines (Godot/GUT, Unity/NUnit, Unreal/UE Automation) and product stacks (Python/pytest, Node/Vitest, Rust/cargo test, Go/go test). Run once during Technical Setup phase before the first sprint begins.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the test-setup skill
What this skill tells your AI
The instructions your AI receives, as published by negentropy-laby/opendoge in .agents/skills/test-setup/SKILL.md and read by ahel’s review.
User Guide
- When to use: Scaffold the test framework and CI/CD pipeline. Supports game engines (Godot/GUT, Unity/NUnit, Unreal/UE Automation) and product stacks (Python/pytest, Node/Vitest, Rust/cargo test, Go/go test). Run once during Technical Setup phase before the first sprint begins.
- Inputs: Command arguments:
/test-setup [force]; project artifacts referenced below; user decisions and approvals before writes. - Outputs: Primary artifacts, reports, or conversation guidance described below; write files only after user approval.
- Memory-bank writes: None.
- Next steps: Follow the workflow hand-off or next-step guidance below; recommendations do not auto-run and require explicit user command/approval.
Test Setup
This skill scaffolds the automated testing infrastructure for the project. It detects the configured engine (game) or technology stack (product), generates the appropriate test runner configuration, creates the standard directory layout, and wires up CI/CD so tests run on every push.
Run this once during the Technical Setup phase, before any implementation begins. A test framework installed at sprint start costs 30 minutes. A test framework installed at sprint four costs 3 sprints.
Output: tests/ directory structure + .github/workflows/tests.yml + one runnable example test file
Phase 1: Detect Technology and Existing State
[通用场景] 1. Detect domain and technology:
- Read
standards/technical-preferences.md. - [游戏专用] Extract
Engine:value. If not configured, stop: "Engine not configured." - [通用产品] Extract
Language:andFramework:values. If not configured, stop: "Stack not configured." - If neither is configured, stop: "Technology not configured. Run
/setup-enginefirst."
[通用场景] 2. Check for existing test infrastructure:
- Glob
tests/— does the directory exist? - Glob
tests/unit/andtests/integration/— do subdirectories exist? - Glob
.github/workflows/— does a CI workflow file exist? - [游戏专用] Glob
tests/gdunit4_runner.gd(Godot),tests/EditMode/(Unity),Source/Tests/(Unreal). - [通用产品] Glob
tests/conftest.py(Python),vitest.config.*(Node),Cargo.tomlwith test profile (Rust),*_test.go(Go).
[通用场景] 3. Report findings:
- "Technology: [engine or stack]. Test directory: [found / not found]. CI workflow: [found / not found]."
- If everything already exists AND
forceargument was not passed: "Test infrastructure appears to be in place. Re-run with/test-setup forceto regenerate. Proceeding will not overwrite existing test files."
If the force argument is passed, skip the "already exists" early-exit and
proceed — but still do not overwrite files that already exist at a given path.
Only create files that are missing.
Phase 2: Present Plan
Based on the technology detected and the existing state, present a plan:
## Test Setup Plan — [Engine or Stack]
I will create the following (skipping any that already exist):
tests/
unit/ — Isolated unit tests
integration/ — Cross-module and end-to-end tests
api/ — Product API contract tests (product projects)
smoke/ — Critical path test list (15-minute manual gate)
README.md — Test framework documentation
[Technology-specific files — see per-technology details below]
.github/workflows/tests.yml — CI: run tests on every push to main
[Technology-specific example test — created so the baseline is runnable]
production/qa/evidence/ — Canonical manual/automated evidence schema
Estimated time: ~5 minutes to create all files.
Ask: "May I create these files? I will not overwrite any test files that already exist at these paths."
Do not proceed without approval.
Phase 3: Create Directory Structure
After approval, create the following files:
tests/README.md
# Test Infrastructure
**Technology**: [engine or stack name + version]
**Test Framework**: [Game: GdUnit4 / Unity / UE Automation | Product: pytest / Vitest / cargo / go]
**CI**: `.github/workflows/tests.yml`
**Setup date**: [date]
## Directory Layout
tests/
unit/ # Isolated unit tests
integration/ # Cross-system and save/load tests
api/ # Product API contract tests (product projects)
smoke/ # Critical path test list for /smoke-check gate
# Manual and release evidence lives in production/qa/evidence/
## Running Tests
[Technology-specific command — see below]
## Test Naming
[Game] Game naming: **Files**: `[system]_[feature]_test.[ext]`, **Functions**: `test_[scenario]_[expected]`, **Example**: `combat_damage_test.gd`
[Product] Product naming: **Files**: `test_[module]_[feature].py` / `[feature].test.ts` / `[feature]_test.rs`, **Example**: `test_user_service.py`
## Story Type → Test Evidence
[Game] Game evidence:
| Story Type | Required Evidence | Location |
|---|---|---|
| Logic | Automated unit test — must pass | `tests/unit/[system]/` |
| Integration | Integration test OR playtest doc | `tests/integration/[system]/` |
| Visual/Feel | Screenshot + lead sign-off | `production/qa/evidence/manual/` |
| UI | Manual walkthrough OR interaction test | `production/qa/evidence/manual/` |
| Config/Data | Smoke check pass | `production/qa/smoke-*.md` |
[Product] Product evidence:
| Story Type | Required Evidence | Location |
|---|---|---|
| Logic | Automated unit test | `tests/unit/[module]/` |
| Integration | Integration test OR API contract test | `tests/integration/[module]/` |
| UI | Screenshot OR interaction test | `production/qa/evidence/manual/` |
| CLI | Smoke command output | `production/qa/evidence/smoke/` |
| API | Contract test | `tests/api/` |
| Config/Data | Migration test OR smoke check | `production/qa/smoke-*.md` |
## CI
Tests run automatically on every push to `main` and on every pull request.
A failed test suite blocks merging.
Technology-specific template selection
Select exactly one scaffold family from the detected domain and technology.
Do not write multiple competing CI templates to .github/workflows/tests.yml.
- [游戏专用] If an engine is detected, create only the engine scaffold and game smoke seed for that engine.
- [通用产品] If a product stack is detected, create only the stack scaffold and product smoke seed for the detected language/framework.
- Unknown: stop and ask the user to run
/setup-engine; do not guess.
Engine-specific files
Godot 4 (Engine: Godot)
Create tests/gdunit4_runner.gd:
# GdUnit4 test runner — invoked by CI and /smoke-check
# Usage: godot --headless --script tests/gdunit4_runner.gd
extends SceneTree
func _init() -> void:
var runner := load("res://" + "addons/gdunit4/GdUnitRunner.gd")
if runner == null:
push_error("GdUnit4 not found. Install via AssetLib or addons/.")
quit(1)
return
var instance = runner.new()
instance.run_tests()
quit(0)
Create tests/unit/.gdignore_placeholder with content:
# Unit tests go here — one subdirectory per system (e.g., tests/unit/combat/)
Create tests/integration/.gdignore_placeholder with content:
# Integration tests go here — one subdirectory per system
Create tests/unit/example_movement_test.gd:
extends GdUnitTestSuite
func test_example_vector_length() -> void:
assert_float(Vector2(3, 4).length()).is_equal_approx(5.0, 0.001)
Note in the README: Installing GdUnit4
1. Open Godot → AssetLib → search "GdUnit4" → Download & Install
2. Enable the plugin: Project → Project Settings → Plugins → GdUnit4 ✓
3. Restart the editor
4. Verify that the Godot addons directory contains `gdunit4/`
Unity (Engine: Unity)
Create tests/EditMode/ placeholder file tests/EditMode/README.md:
# Edit Mode Tests
Unit tests that run without entering Play Mode.
Use for pure logic: formulas, state machines, data validation.
Assembly definition required: `tests/EditMode/EditModeTests.asmdef`
Create tests/PlayMode/README.md:
# Play Mode Tests
Integration tests that run in a real game scene.
Use for cross-system interactions, physics, and coroutines.
Assembly definition required: `tests/PlayMode/PlayModeTests.asmdef`
Create tests/EditMode/ExampleTests.cs:
using NUnit.Framework;
public class ExampleTests
{
[Test]
public void Example_Addition_Works()
{
Assert.AreEqual(4, 2 + 2);
}
}
Note in the README: Enabling Unity Test Framework
Window → General → Test Runner
(Unity Test Framework is included by default in Unity 2019+)
Unreal Engine (Engine: Unreal or Engine: UE5)
Create Source/Tests/README.md:
# Unreal Automation Tests
Tests use the UE Automation Testing Framework.
Run via: Session Frontend → Automation → select "MyGame." tests
Or headlessly: UnrealEditor -nullrhi -ExecCmds="Automation RunTests MyGame.; Quit"
Test class naming: F[SystemName]Test
Test category naming: "MyGame.[System].[Feature]"
Create Source/Tests/ExampleAutomationTest.cpp:
#include "Misc/AutomationTest.h"
IMPLEMENT_SIMPLE_AUTOMATION_TEST(
FExampleAutomationTest,
"MyGame.Example.Baseline",
EAutomationTestFlags::EditorContext | EAutomationTestFlags::EngineFilter
)
bool FExampleAutomationTest::RunTest(const FString& Parameters)
{
TestEqual(TEXT("Example arithmetic baseline"), 2 + 2, 4);
return true;
}
Phase 4: Create CI/CD Workflow
Godot 4
Create .github/workflows/tests.yml:
name: Automated Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Run GdUnit4 Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
lfs: true
- name: Run GdUnit4 Tests
uses: MikeSchulze/gdUnit4-action@v1
with:
godot-version: '[VERSION FROM docs/engine-reference/godot/VERSION.md]'
paths: |
tests/unit
tests/integration
report-name: test-results
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: reports/
Unity
Create .github/workflows/tests.yml:
name: Automated Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Run Unity Tests
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
lfs: true
- name: Run Edit Mode Tests
uses: game-ci/unity-test-runner@v4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with:
testMode: editmode
artifactsPath: test-results/editmode
- name: Run Play Mode Tests
uses: game-ci/unity-test-runner@v4
env:
UNITY_LICENSE: ${{ secrets.UNITY_LICENSE }}
with:
testMode: playmode
artifactsPath: test-results/playmode
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: test-results/
Note: Unity CI requires a UNITY_LICENSE secret. Add to GitHub repository
secrets before the first CI run.
Unreal Engine
Create .github/workflows/tests.yml:
name: Automated Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
name: Run UE Automation Tests
runs-on: self-hosted # UE requires a local runner with the editor installed
steps:
- name: Checkout
uses: actions/checkout@v4
with:
lfs: true
- name: Run Automation Tests
run: |
"$UE_EDITOR_PATH" "${{ github.workspace }}/[ProjectName].uproject" \
-nullrhi -nosound \
-ExecCmds="Automation RunTests MyGame.; Quit" \
-log -unattended
shell: bash
- name: Upload Logs
if: always()
uses: actions/upload-artifact@v4
with:
name: test-logs
path: Saved/Logs/
Note: UE CI requires a self-hosted runner with Unreal Editor installed.
Set the UE_EDITOR_PATH environment variable on the runner.
Product-specific files
[通用产品] Product stack scaffold blocks. Create files appropriate to the detected stack.
Python / pytest (Language: Python)
Create pytest.ini:
[pytest]
testpaths = tests
markers =
unit: Unit tests
integration: Integration tests
slow: Slow tests (deselect with -m "not slow")
api: API contract tests
timeout = 30
Create tests/conftest.py:
# Shared fixtures for all tests
import pytest
import tempfile
import os
@pytest.fixture
def temp_dir():
with tempfile.TemporaryDirectory() as d:
yield d
@pytest.fixture
def test_env():
os.environ["TESTING"] = "1"
yield
os.environ.pop("TESTING", None)
Create tests/unit/test_example.py:
def test_example_baseline():
assert 2 + 2 == 4
CI template (Language: Python):
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with: { python-version: "3.12" }
- run: pip install -r requirements.txt
- run: pytest -x --tb=short
Node / Vitest / Jest (Language: TypeScript or Language: JavaScript)
Create vitest.config.ts:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
include: ['tests/**/*.test.ts'],
environment: 'node',
globals: true,
},
})
Create tests/unit/example.test.ts:
import { describe, expect, it } from 'vitest'
describe('example baseline', () => {
it('runs the test harness', () => {
expect(2 + 2).toBe(4)
})
})
CI template (Language: TypeScript):
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "22" }
- run: npm ci
- run: npx vitest run
Rust / cargo test (Language: Rust)
Create tests/unit/example_test.rs:
#[test]
fn example_baseline() {
assert_eq!(2 + 2, 4);
}
Create tests/unit.rs so cargo test discovers the nested baseline file:
#[path = "unit/example_test.rs"]
mod example_test;
CI template:
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
- run: cargo test
Go / go test (Language: Go)
Create tests/unit/example_test.go:
package unit
import "testing"
func TestExampleBaseline(t *testing.T) {
if 2+2 != 4 {
t.Fatal("example baseline failed")
}
}
CI template:
# .github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: "1.22" }
- run: go test ./...
Phase 5: Create Smoke Test Seed
Create tests/smoke/critical-paths.md. Write only the variant matching the
detected domain — never write both game and product smoke seeds to the same file.
[游戏专用] Game smoke seed:
# Smoke Test: Critical Paths
**Purpose**: Run these 10-15 checks in under 15 minutes before any QA hand-off.
**Run via**: `/smoke-check` (which reads this file)
**Update**: Add new entries when new core systems are implemented.
## Core Stability (always run)
1. Game launches to main menu without crash
2. New game / session can be started from the main menu
3. Main menu responds to all inputs without freezing
## Core Mechanic (update per sprint)
<!-- Add the primary mechanic for each sprint here as it is implemented -->
<!-- Example: "Player can move, jump, and the camera follows correctly" -->
4. [Primary mechanic — update when first core system is implemented]
## Data Integrity
5. Save game completes without error (once save system is implemented)
6. Load game restores correct state (once load system is implemented)
## Performance
7. No visible frame rate drops on target hardware (60fps target)
8. No memory growth over 5 minutes of play (once core loop is implemented)
[通用产品] Product smoke seed:
Create tests/smoke/critical-paths.md with product-appropriate entries:
# Smoke Test: Critical Paths
**Purpose**: Run these 10-15 checks in under 15 minutes before any QA hand-off.
**Run via**: `/smoke-check` (which reads this file)
## Core Stability (always run)
**API:**
1. Health endpoint returns 200
2. Auth endpoint accepts valid credentials and returns token
3. Core GET endpoint returns expected schema (200)
**CLI:**
1. `--help` prints usage without error
2. Core command executes with default flags
3. `--version` prints the correct version
**Web:**
1. Homepage loads (200) without console errors
2. Core navigation works (no 404)
3. Core form submits successfully
## Core Workflow (update per sprint)
4. [Primary workflow — update when first core module is implemented]
## Data Integrity
5. Database migrations run cleanly against fresh instance
6. Seed data / fixtures load without constraint errors
## Performance
7. API response <500ms p95 on core endpoint
8. No unbounded memory growth over 5 minutes of sustained load (once core workflow is implemented)
Phase 6: Post-Setup Summary
After writing all files, report:
Test infrastructure created for [technology].
Files created:
- tests/README.md
- tests/unit/ (directory)
- tests/integration/ (directory)
- tests/api/ (directory, for product API contract tests)
- tests/smoke/critical-paths.md
- production/qa/evidence/ (canonical evidence schema)
[technology-specific files]
- [technology-specific example test file]
- .github/workflows/tests.yml
Next steps:
1. [Technology-specific setup step: Game — install/configure engine test plugin; Product — run the stack test command locally]
2. Run the generated example test locally to confirm the harness works.
3. Replace or extend the example with your first module test when implementation starts: `tests/unit/[first-module]/[module]_test.[ext]`
4. Run `/qa-plan sprint` before your first sprint to classify stories and set
test evidence requirements
5. `/smoke-check` before every QA hand-off
Gate note: /gate-check Technical Setup → Pre-Production now requires:
- tests/ directory with unit/ and integration/ subdirectories
- .github/workflows/tests.yml
- At least one example test file
This `/test-setup` baseline creates the example test; no separate hand-written
test is required for the setup gate.
`/test-helpers` is an optional enhancement for fixtures, factories, mocks, and
engine/stack helper libraries after this baseline exists; missing helpers are
not a gate blocker.
Verdict: **COMPLETE** — test framework scaffolded and CI/CD wired up.
Collaborative Protocol
- Never overwrite existing test files — only create files that are missing. If a test runner file exists, leave it as-is.
- Always ask before creating files — Phase 2 requires explicit approval.
- Technology detection is non-negotiable — game projects require
Engine:; product projects requireLanguage:and should useFramework:when available. If the relevant technology is not configured, stop and redirect to/setup-engine. Do not guess. forceflag skips the "already exists" early-exit but never overwrites. It means "create any missing files even if the directory already exists."- [游戏专用] For Unity CI, note that the
UNITY_LICENSEsecret must be configured manually. Do not attempt to automate license management.
Signals
- GitHub stars
- 20
- Forks
- 5
- Last commit
- Aug 2026
Advanced
- Catalog kind
- skill
- Gateway key
test-setup- Source
- github.com/negentropy-laby/opendoge