docs: add GUT TDD setup plan
This commit is contained in:
501
docs/superpowers/plans/2026-04-17-gut-tdd-setup.md
Normal file
501
docs/superpowers/plans/2026-04-17-gut-tdd-setup.md
Normal file
@@ -0,0 +1,501 @@
|
|||||||
|
# GUT TDD Setup Implementation Plan
|
||||||
|
|
||||||
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||||
|
|
||||||
|
**Goal:** Install GUT v9.6.0, configure headless test runner, and write tests for the core logic layers (GameState, RoomNavigator, GiftBox FSM) as the TDD foundation for all future sprints.
|
||||||
|
|
||||||
|
**Architecture:** GUT addon installed to `addons/gut/`. Tests live in `test/unit/`. Each testable class gets its own `test_<class>.gd` file. Autoloads and components are instantiated fresh per test using `add_child_autofree()`. Headless runner: `godot --headless -s res://addons/gut/gut_cmdln.gd -gdir=res://test/ -gexit`. Future sprints write failing tests first, then implement.
|
||||||
|
|
||||||
|
**Tech Stack:** Godot 4.6.2, GUT v9.6.0, GDScript static typing, PowerShell (Windows) for download
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 1: Download and install GUT v9.6.0
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `addons/gut/` (extracted from GitHub zip)
|
||||||
|
- Modify: `project.godot`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Download and extract GUT**
|
||||||
|
|
||||||
|
Run in PowerShell from the project root `F:\Development\_gameDev\Cozypaw-Hospital`:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$zipUrl = "https://github.com/bitwes/Gut/archive/refs/tags/v9.6.0.zip"
|
||||||
|
$zipPath = "$env:TEMP\gut_v9.6.0.zip"
|
||||||
|
$extractPath = "$env:TEMP\gut_extract_v9"
|
||||||
|
Invoke-WebRequest -Uri $zipUrl -OutFile $zipPath
|
||||||
|
Expand-Archive -Path $zipPath -DestinationPath $extractPath -Force
|
||||||
|
Copy-Item -Path "$extractPath\Gut-9.6.0\addons\gut" -Destination "addons\gut" -Recurse
|
||||||
|
Remove-Item -Path $zipPath, $extractPath -Recurse -Force
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `addons/gut/` now contains `plugin.cfg`, `gut.gd`, `gut_cmdln.gd`, and many other files.
|
||||||
|
|
||||||
|
Verify:
|
||||||
|
```powershell
|
||||||
|
ls addons/gut/ | Select-Object -First 10
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Enable GUT plugin in project.godot**
|
||||||
|
|
||||||
|
In `project.godot`, add the `[editor_plugins]` section at the end of the file:
|
||||||
|
|
||||||
|
```
|
||||||
|
[editor_plugins]
|
||||||
|
|
||||||
|
enabled=PackedStringArray("res://addons/gut/plugin.cfg")
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd "F:/Development/_gameDev/Cozypaw-Hospital"
|
||||||
|
git add addons/gut/ project.godot
|
||||||
|
git commit -m "chore(tooling): install GUT v9.6.0 test framework"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 2: Test infrastructure and smoke test
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `test/unit/test_smoke.gd`
|
||||||
|
- Create: `.gutconfig.json`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create test directory and smoke test**
|
||||||
|
|
||||||
|
Create `test/unit/test_smoke.gd`:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
## Smoke test — verifies GUT is installed and the test runner works.
|
||||||
|
extends GutTest
|
||||||
|
|
||||||
|
|
||||||
|
func test_gut_is_working() -> void:
|
||||||
|
assert_true(true)
|
||||||
|
|
||||||
|
|
||||||
|
func test_basic_arithmetic() -> void:
|
||||||
|
assert_eq(1 + 1, 2)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Create GUT config file**
|
||||||
|
|
||||||
|
Create `.gutconfig.json` in the project root `F:\Development\_gameDev\Cozypaw-Hospital`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dirs": ["res://test/"],
|
||||||
|
"prefix": "test_",
|
||||||
|
"suffix": ".gd",
|
||||||
|
"include_subdirs": true,
|
||||||
|
"log_level": 1,
|
||||||
|
"export_path": ""
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 3: Run the smoke test headlessly**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"F:/Development/_tools/Godot_v4.6.2-stable_win64/Godot_v4.6.2-stable_win64.exe" --headless -s res://addons/gut/gut_cmdln.gd -gdir=res://test/ -gexit 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: output ends with something like `All tests passed` or `0 failures`. Exit code 0.
|
||||||
|
|
||||||
|
If Godot needs to import assets first (first run with new files), run once with `--import` and ignore output, then re-run the test command above:
|
||||||
|
```bash
|
||||||
|
"F:/Development/_tools/Godot_v4.6.2-stable_win64/Godot_v4.6.2-stable_win64.exe" --headless --import 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 4: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add test/unit/test_smoke.gd .gutconfig.json
|
||||||
|
git commit -m "chore(tooling): add GUT test infrastructure and smoke test"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 3: GameState tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `test/unit/test_game_state.gd`
|
||||||
|
|
||||||
|
The implementation (`scripts/autoload/GameState.gd`) already exists. These tests verify it behaves correctly and serve as a regression baseline.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the test file**
|
||||||
|
|
||||||
|
`test/unit/test_game_state.gd`:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
## Tests for GameState — character positions, object states, save/load round-trip.
|
||||||
|
extends GutTest
|
||||||
|
|
||||||
|
const GameStateScript: GDScript = preload("res://scripts/autoload/GameState.gd")
|
||||||
|
|
||||||
|
var _state: Node
|
||||||
|
|
||||||
|
|
||||||
|
func before_each() -> void:
|
||||||
|
_state = GameStateScript.new()
|
||||||
|
add_child_autofree(_state)
|
||||||
|
|
||||||
|
|
||||||
|
func test_get_character_position_default_is_zero() -> void:
|
||||||
|
assert_eq(_state.get_character_position("bunny_01"), Vector2.ZERO)
|
||||||
|
|
||||||
|
|
||||||
|
func test_has_character_position_returns_false_for_unknown_id() -> void:
|
||||||
|
assert_false(_state.has_character_position("bunny_01"))
|
||||||
|
|
||||||
|
|
||||||
|
func test_set_character_position_stores_value() -> void:
|
||||||
|
_state.set_character_position("bunny_01", Vector2(100.0, 200.0))
|
||||||
|
assert_eq(_state.get_character_position("bunny_01"), Vector2(100.0, 200.0))
|
||||||
|
|
||||||
|
|
||||||
|
func test_has_character_position_returns_true_after_set() -> void:
|
||||||
|
_state.set_character_position("bunny_01", Vector2(50.0, 50.0))
|
||||||
|
assert_true(_state.has_character_position("bunny_01"))
|
||||||
|
|
||||||
|
|
||||||
|
func test_set_character_position_emits_character_moved() -> void:
|
||||||
|
watch_signals(_state)
|
||||||
|
_state.set_character_position("bunny_01", Vector2(100.0, 200.0))
|
||||||
|
assert_signal_emitted(_state, "character_moved")
|
||||||
|
|
||||||
|
|
||||||
|
func test_set_character_position_emits_state_changed() -> void:
|
||||||
|
watch_signals(_state)
|
||||||
|
_state.set_character_position("bunny_01", Vector2(100.0, 200.0))
|
||||||
|
assert_signal_emitted(_state, "state_changed")
|
||||||
|
|
||||||
|
|
||||||
|
func test_get_object_state_default_is_idle() -> void:
|
||||||
|
assert_eq(_state.get_object_state("gift_box_1"), "idle")
|
||||||
|
|
||||||
|
|
||||||
|
func test_set_object_state_stores_value() -> void:
|
||||||
|
_state.set_object_state("gift_box_1", "open")
|
||||||
|
assert_eq(_state.get_object_state("gift_box_1"), "open")
|
||||||
|
|
||||||
|
|
||||||
|
func test_set_object_state_emits_state_changed() -> void:
|
||||||
|
watch_signals(_state)
|
||||||
|
_state.set_object_state("gift_box_1", "open")
|
||||||
|
assert_signal_emitted(_state, "state_changed")
|
||||||
|
|
||||||
|
|
||||||
|
func test_save_data_round_trip_character_position() -> void:
|
||||||
|
_state.set_character_position("bunny_01", Vector2(100.0, 200.0))
|
||||||
|
var data: Dictionary = _state.get_save_data()
|
||||||
|
_state.set_character_position("bunny_01", Vector2.ZERO)
|
||||||
|
_state.apply_save_data(data)
|
||||||
|
assert_eq(_state.get_character_position("bunny_01"), Vector2(100.0, 200.0))
|
||||||
|
|
||||||
|
|
||||||
|
func test_save_data_round_trip_object_state() -> void:
|
||||||
|
_state.set_object_state("gift_box_1", "open")
|
||||||
|
var data: Dictionary = _state.get_save_data()
|
||||||
|
_state.set_object_state("gift_box_1", "idle")
|
||||||
|
_state.apply_save_data(data)
|
||||||
|
assert_eq(_state.get_object_state("gift_box_1"), "open")
|
||||||
|
|
||||||
|
|
||||||
|
func test_apply_save_data_with_empty_dict_does_not_crash() -> void:
|
||||||
|
_state.set_character_position("bunny_01", Vector2(10.0, 20.0))
|
||||||
|
_state.apply_save_data({})
|
||||||
|
assert_eq(_state.get_character_position("bunny_01"), Vector2.ZERO)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"F:/Development/_tools/Godot_v4.6.2-stable_win64/Godot_v4.6.2-stable_win64.exe" --headless -s res://addons/gut/gut_cmdln.gd -gdir=res://test/ -gexit 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: All 12 GameState tests pass. 0 failures.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add test/unit/test_game_state.gd
|
||||||
|
git commit -m "test(game-state): add unit tests for character positions, object states, and save round-trip"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 4: RoomNavigator tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `test/unit/test_room_navigator.gd`
|
||||||
|
|
||||||
|
`RoomNavigator` extends `Node` (no `class_name`). Each test gets a fresh instance with its own Camera2D. State variables (`_current_floor`, `_current_room`, `_is_at_home`) are set synchronously before tweens start — assertions work immediately without awaiting tween completion.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the test file**
|
||||||
|
|
||||||
|
`test/unit/test_room_navigator.gd`:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
## Tests for RoomNavigator — floor/room state, home/hospital navigation logic.
|
||||||
|
extends GutTest
|
||||||
|
|
||||||
|
const RoomNavigatorScript: GDScript = preload("res://scripts/systems/room_navigator.gd")
|
||||||
|
|
||||||
|
var _nav: Node
|
||||||
|
var _camera: Camera2D
|
||||||
|
|
||||||
|
|
||||||
|
func before_each() -> void:
|
||||||
|
_nav = RoomNavigatorScript.new()
|
||||||
|
add_child_autofree(_nav)
|
||||||
|
_camera = Camera2D.new()
|
||||||
|
add_child_autofree(_camera)
|
||||||
|
_nav.initialize(_camera)
|
||||||
|
|
||||||
|
|
||||||
|
func test_initial_floor_is_zero() -> void:
|
||||||
|
assert_eq(_nav.get_current_floor(), 0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_initial_room_is_zero() -> void:
|
||||||
|
assert_eq(_nav.get_current_room(), 0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_is_at_home_starts_false() -> void:
|
||||||
|
assert_false(_nav.is_at_home())
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_room_updates_floor() -> void:
|
||||||
|
_nav.go_to_room(1, 2)
|
||||||
|
assert_eq(_nav.get_current_floor(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_room_updates_room() -> void:
|
||||||
|
_nav.go_to_room(1, 2)
|
||||||
|
assert_eq(_nav.get_current_room(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_room_same_position_when_not_at_home_is_noop() -> void:
|
||||||
|
_nav.go_to_room(1, 2)
|
||||||
|
var camera_pos: Vector2 = _camera.position
|
||||||
|
_nav.go_to_room(1, 2)
|
||||||
|
assert_eq(_camera.position, camera_pos)
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_floor_navigates_to_room_zero() -> void:
|
||||||
|
_nav.go_to_floor(2)
|
||||||
|
assert_eq(_nav.get_current_floor(), 2)
|
||||||
|
assert_eq(_nav.get_current_room(), 0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_home_sets_is_at_home_true() -> void:
|
||||||
|
_nav.go_to_home()
|
||||||
|
assert_true(_nav.is_at_home())
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_home_twice_is_noop_on_second_call() -> void:
|
||||||
|
_nav.go_to_home()
|
||||||
|
var camera_pos: Vector2 = _camera.position
|
||||||
|
_nav.go_to_home()
|
||||||
|
assert_eq(_camera.position, camera_pos)
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_hospital_clears_is_at_home() -> void:
|
||||||
|
_nav.go_to_home()
|
||||||
|
_nav.go_to_hospital()
|
||||||
|
assert_false(_nav.is_at_home())
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_hospital_restores_last_floor() -> void:
|
||||||
|
_nav.go_to_room(1, 2)
|
||||||
|
_nav.go_to_home()
|
||||||
|
_nav.go_to_hospital()
|
||||||
|
assert_eq(_nav.get_current_floor(), 1)
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_hospital_restores_last_room() -> void:
|
||||||
|
_nav.go_to_room(1, 2)
|
||||||
|
_nav.go_to_home()
|
||||||
|
_nav.go_to_hospital()
|
||||||
|
assert_eq(_nav.get_current_room(), 2)
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_hospital_when_not_at_home_is_noop() -> void:
|
||||||
|
_nav.go_to_room(1, 2)
|
||||||
|
var camera_pos: Vector2 = _camera.position
|
||||||
|
_nav.go_to_hospital()
|
||||||
|
assert_eq(_camera.position, camera_pos)
|
||||||
|
|
||||||
|
|
||||||
|
func test_go_to_room_after_home_clears_is_at_home() -> void:
|
||||||
|
_nav.go_to_home()
|
||||||
|
_nav.go_to_room(0, 0)
|
||||||
|
assert_false(_nav.is_at_home())
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"F:/Development/_tools/Godot_v4.6.2-stable_win64/Godot_v4.6.2-stable_win64.exe" --headless -s res://addons/gut/gut_cmdln.gd -gdir=res://test/ -gexit 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: All RoomNavigator + GameState + smoke tests pass. 0 failures.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add test/unit/test_room_navigator.gd
|
||||||
|
git commit -m "test(room-navigator): add unit tests for floor/room state and home/hospital navigation"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 5: GiftBox FSM tests
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Create: `test/unit/test_gift_box.gd`
|
||||||
|
|
||||||
|
GiftBox is instantiated from its scene (so `_ready()` runs and hides the Gift node). State transitions are tested synchronously. Tween visual outcomes (alpha values mid-animation) are not tested — only state machine transitions.
|
||||||
|
|
||||||
|
- [ ] **Step 1: Create the test file**
|
||||||
|
|
||||||
|
`test/unit/test_gift_box.gd`:
|
||||||
|
|
||||||
|
```gdscript
|
||||||
|
## Tests for GiftBox — CLOSED/OPENING/OPEN state machine transitions.
|
||||||
|
extends GutTest
|
||||||
|
|
||||||
|
var _box: GiftBox
|
||||||
|
|
||||||
|
|
||||||
|
func before_each() -> void:
|
||||||
|
_box = preload("res://scenes/objects/GiftBox.tscn").instantiate() as GiftBox
|
||||||
|
add_child_autofree(_box)
|
||||||
|
|
||||||
|
|
||||||
|
func test_initial_state_is_closed() -> void:
|
||||||
|
assert_eq(_box._state, GiftBox.State.CLOSED)
|
||||||
|
|
||||||
|
|
||||||
|
func test_ready_hides_gift_node() -> void:
|
||||||
|
var gift: Node2D = _box.get_node("Gift") as Node2D
|
||||||
|
assert_eq(gift.modulate.a, 0.0)
|
||||||
|
|
||||||
|
|
||||||
|
func test_start_opening_transitions_to_opening() -> void:
|
||||||
|
_box._start_opening()
|
||||||
|
assert_eq(_box._state, GiftBox.State.OPENING)
|
||||||
|
|
||||||
|
|
||||||
|
func test_on_lid_opened_transitions_to_open() -> void:
|
||||||
|
_box._start_opening()
|
||||||
|
_box._on_lid_opened()
|
||||||
|
assert_eq(_box._state, GiftBox.State.OPEN)
|
||||||
|
|
||||||
|
|
||||||
|
func test_input_ignored_when_state_is_opening() -> void:
|
||||||
|
_box._state = GiftBox.State.OPENING
|
||||||
|
var event: InputEventScreenTouch = InputEventScreenTouch.new()
|
||||||
|
event.pressed = true
|
||||||
|
event.position = _box.global_position
|
||||||
|
_box._input(event)
|
||||||
|
assert_eq(_box._state, GiftBox.State.OPENING)
|
||||||
|
|
||||||
|
|
||||||
|
func test_input_ignored_when_state_is_open() -> void:
|
||||||
|
_box._state = GiftBox.State.OPEN
|
||||||
|
var event: InputEventScreenTouch = InputEventScreenTouch.new()
|
||||||
|
event.pressed = true
|
||||||
|
event.position = _box.global_position
|
||||||
|
_box._input(event)
|
||||||
|
assert_eq(_box._state, GiftBox.State.OPEN)
|
||||||
|
|
||||||
|
|
||||||
|
func test_tap_outside_hitbox_does_not_open() -> void:
|
||||||
|
var event: InputEventScreenTouch = InputEventScreenTouch.new()
|
||||||
|
event.pressed = true
|
||||||
|
event.position = _box.global_position + Vector2(200.0, 200.0)
|
||||||
|
_box._input(event)
|
||||||
|
assert_eq(_box._state, GiftBox.State.CLOSED)
|
||||||
|
|
||||||
|
|
||||||
|
func test_release_event_does_not_open() -> void:
|
||||||
|
var event: InputEventScreenTouch = InputEventScreenTouch.new()
|
||||||
|
event.pressed = false
|
||||||
|
event.position = _box.global_position
|
||||||
|
_box._input(event)
|
||||||
|
assert_eq(_box._state, GiftBox.State.CLOSED)
|
||||||
|
```
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run tests**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"F:/Development/_tools/Godot_v4.6.2-stable_win64/Godot_v4.6.2-stable_win64.exe" --headless -s res://addons/gut/gut_cmdln.gd -gdir=res://test/ -gexit 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: All GiftBox + earlier tests pass. 0 failures.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add test/unit/test_gift_box.gd
|
||||||
|
git commit -m "test(gift-box): add unit tests for CLOSED/OPENING/OPEN state machine"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Task 6: Update CLAUDE.md — TDD conventions
|
||||||
|
|
||||||
|
**Files:**
|
||||||
|
- Modify: `CLAUDE.md`
|
||||||
|
|
||||||
|
- [ ] **Step 1: Replace the Tests section in CLAUDE.md**
|
||||||
|
|
||||||
|
Find the block starting with `### Tests` and ending before `### Tests` or the next `##` section (whichever comes first). Replace the entire block with:
|
||||||
|
|
||||||
|
~~~markdown
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
**Framework:** GUT v9.6.0 (`addons/gut/`)
|
||||||
|
|
||||||
|
**Headless runner:**
|
||||||
|
|
||||||
|
"F:/Development/_tools/Godot_v4.6.2-stable_win64/Godot_v4.6.2-stable_win64.exe" --headless -s res://addons/gut/gut_cmdln.gd -gdir=res://test/ -gexit
|
||||||
|
|
||||||
|
**TDD-Workflow (ab Sprint 15):**
|
||||||
|
1. Failing test schreiben für die neue Logik
|
||||||
|
2. Test ausführen → muss FAIL sein
|
||||||
|
3. Minimalimplementierung schreiben
|
||||||
|
4. Test ausführen → muss PASS sein
|
||||||
|
5. Committen
|
||||||
|
|
||||||
|
**Was wird getestet:**
|
||||||
|
- ✅ Autoloads: `GameState`, `RoomNavigator`, `SaveManager`
|
||||||
|
- ✅ State-Machines: `GiftBox`, `TeaPot`, `Cradle`, `DeliveryBed`, etc.
|
||||||
|
- ✅ Reine Logik-Berechnungen (Koordinaten, Serialisierung)
|
||||||
|
- ❌ Tween-Animationen (visuell, kein Assert möglich)
|
||||||
|
- ❌ Touch-Input (manuelle Tests auf Tablet)
|
||||||
|
|
||||||
|
**Test-Dateien:** `test/unit/test_<class_name>.gd`, extends `GutTest`
|
||||||
|
|
||||||
|
**Device-Tests:**
|
||||||
|
- Scene-Tests manuell auf echtem Android-Tablet pro Sprint
|
||||||
|
- Kinder als UAT — wöchentliches Zeigen der Builds
|
||||||
|
~~~
|
||||||
|
|
||||||
|
- [ ] **Step 2: Run all tests to confirm baseline passes**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
"F:/Development/_tools/Godot_v4.6.2-stable_win64/Godot_v4.6.2-stable_win64.exe" --headless -s res://addons/gut/gut_cmdln.gd -gdir=res://test/ -gexit 2>&1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: All tests pass (smoke + GameState + RoomNavigator + GiftBox). 0 failures.
|
||||||
|
|
||||||
|
- [ ] **Step 3: Commit**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add CLAUDE.md
|
||||||
|
git commit -m "docs(claude): update testing conventions to reflect GUT TDD workflow"
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user