Update "Writing-Tests.-"
parent
390d2feb54
commit
14438382fe
2 changed files with 338 additions and 512 deletions
338
Writing-Tests.-.md
Normal file
338
Writing-Tests.-.md
Normal file
|
|
@ -0,0 +1,338 @@
|
|||
# Writing Tests
|
||||
|
||||
Guide to creating automated tests for McRogueFace using Python and the automation API.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Test Location:** `tests/` directory (NOT `build/tests/` - that gets shipped!)
|
||||
|
||||
**Test Types:**
|
||||
1. **Direct Execution** - No game loop, immediate results
|
||||
2. **Timer-Based** - Requires rendering/game loop
|
||||
|
||||
**Key Tools:**
|
||||
- `mcrfpy.automation` - Screenshot and input automation
|
||||
- `--headless` flag - Run without display
|
||||
- `--exec` flag - Execute specific script
|
||||
|
||||
**Format:** See CLAUDE.md "Testing Guidelines" section
|
||||
|
||||
---
|
||||
|
||||
## Test Type 1: Direct Execution
|
||||
|
||||
For tests that don't need rendering or game state.
|
||||
|
||||
### Template
|
||||
|
||||
```python
|
||||
"""Test description goes here."""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_feature():
|
||||
# Setup
|
||||
scene = mcrfpy.Scene("test")
|
||||
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
||||
scene.children.append(frame)
|
||||
|
||||
# Test
|
||||
frame.x = 42
|
||||
assert frame.x == 42, f"Expected 42, got {frame.x}"
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
if test_feature():
|
||||
print("PASS")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
print(f"FAIL: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
```bash
|
||||
cd build
|
||||
./mcrogueface --headless --exec ../tests/unit/test_myfeature.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Type 2: Timer-Based Tests
|
||||
|
||||
For tests requiring rendering, screenshots, or elapsed time.
|
||||
|
||||
### Template
|
||||
|
||||
```python
|
||||
"""Test requiring game loop."""
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
# Setup scene BEFORE game loop starts
|
||||
scene = mcrfpy.Scene("test")
|
||||
ui = scene.children
|
||||
|
||||
frame = mcrfpy.Frame(pos=(100, 100), size=(200, 150),
|
||||
fill_color=mcrfpy.Color(255, 0, 0))
|
||||
ui.append(frame)
|
||||
mcrfpy.current_scene = scene
|
||||
|
||||
def run_test(timer, runtime):
|
||||
"""Timer callback receives (timer_object, runtime_ms)."""
|
||||
automation.screenshot("test_output.png")
|
||||
|
||||
# Verify results
|
||||
assert frame.x == 100
|
||||
|
||||
print("PASS")
|
||||
sys.exit(0) # MUST exit!
|
||||
|
||||
# Schedule test to run after game loop starts
|
||||
test_timer = mcrfpy.Timer("test_runner", run_test, 100)
|
||||
```
|
||||
|
||||
**Important:** Timer callbacks receive `(timer, runtime_ms)`. Screenshots only work after rendering starts.
|
||||
|
||||
### Example: Testing Click Events
|
||||
|
||||
```python
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
clicks_received = []
|
||||
|
||||
scene = mcrfpy.Scene("test")
|
||||
frame = mcrfpy.Frame(pos=(100, 100), size=(200, 150),
|
||||
fill_color=mcrfpy.Color(0, 255, 0))
|
||||
|
||||
def on_click(pos, button, action):
|
||||
clicks_received.append((pos, button, action))
|
||||
|
||||
frame.on_click = on_click
|
||||
scene.children.append(frame)
|
||||
mcrfpy.current_scene = scene
|
||||
|
||||
def run_test(timer, runtime):
|
||||
# Simulate click on frame center
|
||||
automation.click(200, 175)
|
||||
# Give it a frame to process
|
||||
verify_timer = mcrfpy.Timer("verify", verify_results, 32)
|
||||
|
||||
def verify_results(timer, runtime):
|
||||
assert len(clicks_received) > 0, "No clicks received!"
|
||||
pos, button, action = clicks_received[0]
|
||||
print(f"Click received at ({pos.x}, {pos.y})")
|
||||
print("PASS")
|
||||
sys.exit(0)
|
||||
|
||||
test_timer = mcrfpy.Timer("test", run_test, 100)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automation API
|
||||
|
||||
### Screenshots
|
||||
|
||||
```python
|
||||
from mcrfpy import automation
|
||||
|
||||
# Take screenshot (synchronous in headless mode)
|
||||
automation.screenshot("output.png")
|
||||
```
|
||||
|
||||
### Mouse Input
|
||||
|
||||
```python
|
||||
from mcrfpy import automation
|
||||
|
||||
automation.click(x, y) # Left click at position
|
||||
automation.move(x, y) # Move mouse to position
|
||||
```
|
||||
|
||||
### Keyboard Input
|
||||
|
||||
```python
|
||||
from mcrfpy import automation
|
||||
|
||||
automation.keypress(key_code) # Simulate key press
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Patterns
|
||||
|
||||
### Pattern 1: Property Round-Trip
|
||||
|
||||
```python
|
||||
def test_property_roundtrip():
|
||||
obj = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
||||
test_values = [0, 50, 100, 255, 127]
|
||||
for value in test_values:
|
||||
obj.x = value
|
||||
assert obj.x == value, f"Failed for {value}"
|
||||
```
|
||||
|
||||
### Pattern 2: Exception Testing
|
||||
|
||||
```python
|
||||
def test_invalid_input():
|
||||
grid = mcrfpy.Grid(grid_size=(10, 10), pos=(0, 0), size=(160, 160))
|
||||
try:
|
||||
grid.at(-1, -1)
|
||||
assert False, "Should have raised exception"
|
||||
except Exception:
|
||||
pass # Expected
|
||||
```
|
||||
|
||||
### Pattern 3: Grid Operations
|
||||
|
||||
```python
|
||||
def test_grid_walkable():
|
||||
grid = mcrfpy.Grid(grid_size=(10, 10), pos=(0, 0), size=(160, 160))
|
||||
grid.at(5, 5).walkable = True
|
||||
assert grid.at(5, 5).walkable == True
|
||||
|
||||
grid.at(5, 5).walkable = False
|
||||
assert grid.at(5, 5).walkable == False
|
||||
```
|
||||
|
||||
### Pattern 4: Entity Lifecycle
|
||||
|
||||
```python
|
||||
def test_entity_lifecycle():
|
||||
grid = mcrfpy.Grid(grid_size=(20, 20), pos=(0, 0), size=(320, 320))
|
||||
|
||||
# Create and add
|
||||
entity = mcrfpy.Entity(grid_pos=(5, 5), sprite_index=0)
|
||||
assert entity.grid is None
|
||||
grid.entities.append(entity)
|
||||
assert entity.grid is not None
|
||||
|
||||
# Move
|
||||
entity.grid_x = 10
|
||||
assert entity.grid_x == 10
|
||||
|
||||
# Remove
|
||||
entity.die()
|
||||
assert entity.grid is None
|
||||
```
|
||||
|
||||
### Pattern 5: Callback Setup
|
||||
|
||||
```python
|
||||
def test_click_callbacks():
|
||||
frame = mcrfpy.Frame(pos=(100, 100), size=(200, 150))
|
||||
clicks = []
|
||||
|
||||
# on_click receives (pos: Vector, button: MouseButton, action: InputState)
|
||||
def on_click(pos, button, action):
|
||||
if button == mcrfpy.MouseButton.LEFT and action == mcrfpy.InputState.PRESSED:
|
||||
clicks.append((pos.x, pos.y))
|
||||
|
||||
frame.on_click = on_click
|
||||
# on_enter/on_exit receive (pos: Vector)
|
||||
frame.on_enter = lambda pos: None
|
||||
frame.on_exit = lambda pos: None
|
||||
```
|
||||
|
||||
### Pattern 6: Timer Usage
|
||||
|
||||
```python
|
||||
def test_timer():
|
||||
fired = [False]
|
||||
|
||||
def on_timer(timer, runtime):
|
||||
fired[0] = True
|
||||
|
||||
t = mcrfpy.Timer("test_timer", on_timer, 100)
|
||||
|
||||
# In headless mode, use step() to advance time
|
||||
mcrfpy.step(0.2) # 200ms
|
||||
assert fired[0], "Timer should have fired"
|
||||
|
||||
t.stop() # Clean up
|
||||
```
|
||||
|
||||
### Pattern 7: Animation Testing
|
||||
|
||||
```python
|
||||
def test_animation():
|
||||
frame = mcrfpy.Frame(pos=(0, 0), size=(100, 100))
|
||||
frame.animate("x", 500.0, 2.0, mcrfpy.Easing.EASE_IN_OUT)
|
||||
|
||||
# In headless mode, advance time to test
|
||||
mcrfpy.step(1.0) # Halfway through
|
||||
assert frame.x > 0 # Should have moved
|
||||
|
||||
mcrfpy.step(1.0) # Complete
|
||||
# frame.x should be at or near 500
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test-Driven Development (TDD)
|
||||
|
||||
### TDD Workflow
|
||||
|
||||
1. **Write failing test** - Demonstrates the bug/missing feature
|
||||
2. **Run test** - Verify it fails
|
||||
3. **Implement fix** - Make minimum change to pass
|
||||
4. **Run test** - Verify it passes
|
||||
5. **Refactor** - Clean up
|
||||
6. **Run full suite** - `cd tests && python3 run_tests.py`
|
||||
|
||||
---
|
||||
|
||||
## Testing Best Practices
|
||||
|
||||
### DO:
|
||||
|
||||
- **Test one thing at a time** - Each test function covers one behavior
|
||||
- **Use descriptive names** - `test_entity_moves_to_valid_position()`
|
||||
- **Always exit** - Use `sys.exit(0)` for pass, `sys.exit(1)` for fail
|
||||
- **Test edge cases** - Boundaries, empty states, invalid input
|
||||
- **Clean up timers** - Call `timer.stop()` when done
|
||||
|
||||
### DON'T:
|
||||
|
||||
- **Rely on timing** - Use `step()` in headless mode, not `time.sleep()`
|
||||
- **Forget sys.exit()** in timer tests - Will hang indefinitely
|
||||
- **Test multiple unrelated things** in one test function
|
||||
- **Put tests in build/** - They get shipped with the game
|
||||
|
||||
---
|
||||
|
||||
## Running the Test Suite
|
||||
|
||||
```bash
|
||||
# Full suite
|
||||
cd tests && python3 run_tests.py
|
||||
|
||||
# Single test
|
||||
cd build && ./mcrogueface --headless --exec ../tests/unit/my_test.py
|
||||
|
||||
# Inline test
|
||||
cd build && ./mcrogueface --headless -c "import mcrfpy; print('OK')"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [[Headless-Mode]] - step() function and headless operation
|
||||
- [[Input-and-Events]] - Event handler signatures
|
||||
- [[Animation-System]] - Animation testing
|
||||
- CLAUDE.md - Testing guidelines section
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2026-02-07*
|
||||
512
Writing-Tests.md
512
Writing-Tests.md
|
|
@ -1,512 +0,0 @@
|
|||
# Writing Tests
|
||||
|
||||
Guide to creating automated tests for McRogueFace using Python and the automation API.
|
||||
|
||||
## Quick Reference
|
||||
|
||||
**Test Location:** `tests/` directory (NOT `build/tests/` - that gets shipped!)
|
||||
|
||||
**Test Types:**
|
||||
1. **Direct Execution** - No game loop, immediate results
|
||||
2. **Timer-Based** - Requires rendering/game loop
|
||||
|
||||
**Key Tools:**
|
||||
- `mcrfpy.automation` - Screenshot and input automation
|
||||
- `--headless` flag - Run without display
|
||||
- `--exec` flag - Execute specific script
|
||||
|
||||
**Format:** See CLAUDE.md "Testing Guidelines" section
|
||||
|
||||
---
|
||||
|
||||
## Test Type 1: Direct Execution
|
||||
|
||||
For tests that don't need rendering or game state.
|
||||
|
||||
### Template
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Test description goes here."""
|
||||
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_feature():
|
||||
# Setup
|
||||
obj = mcrfpy.SomeClass()
|
||||
|
||||
# Test
|
||||
obj.property = 42
|
||||
|
||||
# Verify
|
||||
assert obj.property == 42, f"Expected 42, got {obj.property}"
|
||||
|
||||
# More tests...
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
if test_feature():
|
||||
print("PASS")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("FAIL")
|
||||
sys.exit(1)
|
||||
except Exception as e:
|
||||
print(f"FAIL: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
```bash
|
||||
cd build
|
||||
./mcrogueface --headless --exec ../tests/test_myfeature.py
|
||||
```
|
||||
|
||||
### Example: Testing Vector Operations
|
||||
|
||||
```python
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_vector():
|
||||
# Create vector
|
||||
v = mcrfpy.Vector(3.0, 4.0)
|
||||
|
||||
# Test properties
|
||||
assert v.x == 3.0
|
||||
assert v.y == 4.0
|
||||
|
||||
# Test tuple access
|
||||
assert v[0] == 3.0
|
||||
assert v[1] == 4.0
|
||||
|
||||
# Test modification
|
||||
v.x = 10.0
|
||||
assert v.x == 10.0
|
||||
|
||||
print("All vector tests passed")
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
if test_vector():
|
||||
print("PASS")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("FAIL")
|
||||
sys.exit(1)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Test Type 2: Timer-Based Tests
|
||||
|
||||
For tests requiring rendering, screenshots, or game loop.
|
||||
|
||||
### Template
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""Test description - requires game loop."""
|
||||
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
# Setup scene BEFORE game loop starts
|
||||
mcrfpy.createScene("test")
|
||||
|
||||
# Create UI elements
|
||||
frame = mcrfpy.Frame(100, 100, 200, 150)
|
||||
frame.fill_color = mcrfpy.Color(255, 0, 0)
|
||||
mcrfpy.sceneUI("test").append(frame)
|
||||
|
||||
# Switch to test scene
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
def run_test(runtime_ms):
|
||||
"""Timer callback - runs AFTER game loop starts."""
|
||||
|
||||
# Take screenshot
|
||||
automation.screenshot("test_output.png")
|
||||
|
||||
# Perform interactions
|
||||
automation.click(150, 150) # Click on frame
|
||||
|
||||
# Verify results
|
||||
# ... check state, take more screenshots, etc ...
|
||||
|
||||
# Report results
|
||||
print("PASS")
|
||||
sys.exit(0) # MUST exit!
|
||||
|
||||
# Schedule test to run after game loop starts
|
||||
mcrfpy.setTimer("test_runner", run_test, 100) # 100ms delay
|
||||
```
|
||||
|
||||
### Running
|
||||
|
||||
```bash
|
||||
cd build
|
||||
./mcrogueface --headless --exec ../tests/test_rendering.py
|
||||
```
|
||||
|
||||
**Important:** Timer callbacks are essential! Screenshots only work after rendering starts.
|
||||
|
||||
### Example: Testing Click Events
|
||||
|
||||
```python
|
||||
import mcrfpy
|
||||
from mcrfpy import automation
|
||||
import sys
|
||||
|
||||
# Track clicks
|
||||
clicks_received = []
|
||||
|
||||
def on_frame_click(x, y, button, state):
|
||||
clicks_received.append((x, y, button, state))
|
||||
|
||||
# Setup scene
|
||||
mcrfpy.createScene("test")
|
||||
|
||||
frame = mcrfpy.Frame(100, 100, 200, 150)
|
||||
frame.fill_color = mcrfpy.Color(0, 255, 0)
|
||||
frame.click = on_frame_click
|
||||
|
||||
mcrfpy.sceneUI("test").append(frame)
|
||||
mcrfpy.setScene("test")
|
||||
|
||||
def run_test(runtime_ms):
|
||||
# Simulate click on frame center
|
||||
automation.click(200, 175)
|
||||
|
||||
# Give it a frame to process
|
||||
mcrfpy.setTimer("verify", verify_results, 32)
|
||||
|
||||
def verify_results(runtime_ms):
|
||||
# Check click was received
|
||||
assert len(clicks_received) > 0, "No clicks received!"
|
||||
|
||||
x, y, button, state = clicks_received[0]
|
||||
assert button == 1, f"Wrong button: {button}"
|
||||
assert state == True, "Wrong state"
|
||||
|
||||
print(f"Click received at ({x}, {y})")
|
||||
print("PASS")
|
||||
sys.exit(0)
|
||||
|
||||
mcrfpy.setTimer("test", run_test, 100)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Automation API
|
||||
|
||||
### Screenshots
|
||||
|
||||
```python
|
||||
from mcrfpy import automation
|
||||
|
||||
# Take screenshot
|
||||
automation.screenshot("output.png")
|
||||
|
||||
# Screenshots saved to current directory
|
||||
# Use for visual regression testing
|
||||
```
|
||||
|
||||
### Mouse Input
|
||||
|
||||
```python
|
||||
from mcrfpy import automation
|
||||
|
||||
# Click at position
|
||||
automation.click(x, y, button=1) # button: 1=left, 2=middle, 3=right
|
||||
|
||||
# Move mouse
|
||||
automation.move(x, y)
|
||||
```
|
||||
|
||||
### Keyboard Input
|
||||
|
||||
```python
|
||||
from mcrfpy import automation
|
||||
|
||||
# Key press
|
||||
automation.keypress(key_code, pressed=True)
|
||||
|
||||
# Key release
|
||||
automation.keypress(key_code, pressed=False)
|
||||
```
|
||||
|
||||
**Key codes:** See `mcrfpy.Key.*` constants
|
||||
|
||||
---
|
||||
|
||||
## Test-Driven Development (TDD)
|
||||
|
||||
### TDD Workflow
|
||||
|
||||
1. **Write failing test** - Demonstrates the bug/missing feature
|
||||
2. **Run test** - Verify it fails
|
||||
3. **Implement fix** - Make minimum change to pass
|
||||
4. **Run test** - Verify it passes
|
||||
5. **Refactor** - Clean up if needed
|
||||
6. **Run test** - Verify still passes
|
||||
|
||||
### Example: TDD for New Feature
|
||||
|
||||
**Step 1: Write failing test**
|
||||
|
||||
`tests/test_sprite_rotation.py`:
|
||||
```python
|
||||
import mcrfpy
|
||||
|
||||
def test_sprite_rotation():
|
||||
sprite = mcrfpy.Sprite("test.png", 0, 0)
|
||||
|
||||
# This should work but doesn't yet
|
||||
sprite.rotation = 90
|
||||
assert sprite.rotation == 90
|
||||
|
||||
return True
|
||||
|
||||
if __name__ == "__main__":
|
||||
if test_sprite_rotation():
|
||||
print("PASS")
|
||||
else:
|
||||
print("FAIL")
|
||||
```
|
||||
|
||||
**Step 2: Run test - it fails**
|
||||
|
||||
```bash
|
||||
$ ./mcrogueface --headless --exec tests/test_sprite_rotation.py
|
||||
AttributeError: 'Sprite' object has no attribute 'rotation'
|
||||
FAIL
|
||||
```
|
||||
|
||||
**Step 3: Implement feature**
|
||||
|
||||
Add `rotation` property to `src/UISprite.cpp` (see [[Adding-Python-Bindings]])
|
||||
|
||||
**Step 4: Run test - it passes**
|
||||
|
||||
```bash
|
||||
$ ./mcrogueface --headless --exec tests/test_sprite_rotation.py
|
||||
PASS
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing Best Practices
|
||||
|
||||
### DO:
|
||||
|
||||
✅ **Test one thing at a time**
|
||||
```python
|
||||
def test_grid_walkable_setter():
|
||||
grid = mcrfpy.Grid(10, 10, 16, 16)
|
||||
grid.walkable((5, 5), True)
|
||||
# Test only walkable setter, nothing else
|
||||
```
|
||||
|
||||
✅ **Use descriptive test names**
|
||||
```python
|
||||
# Good
|
||||
def test_entity_moves_to_valid_position()
|
||||
|
||||
# Bad
|
||||
def test1()
|
||||
```
|
||||
|
||||
✅ **Clean up after tests**
|
||||
```python
|
||||
def test_feature():
|
||||
# Setup
|
||||
obj = create_test_object()
|
||||
|
||||
try:
|
||||
# Test
|
||||
obj.do_thing()
|
||||
finally:
|
||||
# Cleanup
|
||||
obj.cleanup()
|
||||
```
|
||||
|
||||
✅ **Test edge cases**
|
||||
```python
|
||||
def test_grid_bounds():
|
||||
grid = mcrfpy.Grid(10, 10, 16, 16)
|
||||
|
||||
# Test boundaries
|
||||
grid.at((0, 0)) # Min
|
||||
grid.at((9, 9)) # Max
|
||||
|
||||
# Test out of bounds
|
||||
try:
|
||||
grid.at((10, 10))
|
||||
assert False, "Should have raised exception"
|
||||
except ValueError:
|
||||
pass # Expected
|
||||
```
|
||||
|
||||
### DON'T:
|
||||
|
||||
❌ **Test multiple unrelated things**
|
||||
```python
|
||||
# Bad - testing grid, entity, AND animation
|
||||
def test_everything():
|
||||
grid = mcrfpy.Grid(10, 10, 16, 16)
|
||||
entity = mcrfpy.Entity(5, 5, 0)
|
||||
mcrfpy.animate(entity, "x", 10, 1000, "linear")
|
||||
# Too much!
|
||||
```
|
||||
|
||||
❌ **Forget to exit in timer-based tests**
|
||||
```python
|
||||
def run_test(runtime_ms):
|
||||
automation.screenshot("test.png")
|
||||
print("PASS")
|
||||
# MISSING: sys.exit(0) - will hang!
|
||||
```
|
||||
|
||||
❌ **Rely on timing**
|
||||
```python
|
||||
# Bad - fragile
|
||||
time.sleep(0.5) # Hope animation finished
|
||||
assert entity.x == 100
|
||||
|
||||
# Good - check state
|
||||
while entity.x < 100 and timeout < 1000:
|
||||
time.sleep(0.016)
|
||||
timeout += 16
|
||||
assert entity.x == 100
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Common Testing Patterns
|
||||
|
||||
### Pattern 1: Property Round-Trip
|
||||
|
||||
```python
|
||||
def test_property_roundtrip():
|
||||
obj = mcrfpy.Frame(0, 0, 100, 100)
|
||||
|
||||
# Set various values
|
||||
test_values = [0, 50, 100, 255, 127]
|
||||
|
||||
for value in test_values:
|
||||
obj.x = value
|
||||
assert obj.x == value, f"Failed for {value}"
|
||||
```
|
||||
|
||||
### Pattern 2: Visual Regression
|
||||
|
||||
```python
|
||||
def run_test(runtime_ms):
|
||||
# Render scene
|
||||
automation.screenshot("current.png")
|
||||
|
||||
# Compare with golden image
|
||||
# (Manual comparison or use image diff tool)
|
||||
|
||||
print("Check current.png against golden/expected.png")
|
||||
sys.exit(0)
|
||||
```
|
||||
|
||||
### Pattern 3: Exception Testing
|
||||
|
||||
```python
|
||||
def test_invalid_input():
|
||||
grid = mcrfpy.Grid(10, 10, 16, 16)
|
||||
|
||||
# Test exception is raised
|
||||
try:
|
||||
grid.at((-1, -1)) # Invalid coords
|
||||
assert False, "Should have raised ValueError"
|
||||
except ValueError as e:
|
||||
assert "out of bounds" in str(e).lower()
|
||||
```
|
||||
|
||||
### Pattern 4: State Machine Testing
|
||||
|
||||
```python
|
||||
def test_entity_states():
|
||||
entity = mcrfpy.Entity(0, 0, 0)
|
||||
|
||||
# Initial state
|
||||
assert entity.state == "idle"
|
||||
|
||||
# Transition
|
||||
entity.attack()
|
||||
assert entity.state == "attacking"
|
||||
|
||||
# Complete action
|
||||
entity.finish_attack()
|
||||
assert entity.state == "idle"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Debugging Failed Tests
|
||||
|
||||
### Get More Information
|
||||
|
||||
```python
|
||||
def test_feature():
|
||||
try:
|
||||
obj.do_thing()
|
||||
except Exception as e:
|
||||
print(f"Exception: {e}")
|
||||
print(f"Object state: {obj}")
|
||||
print(f"Type: {type(obj)}")
|
||||
raise # Re-raise to fail test
|
||||
```
|
||||
|
||||
### Visual Debugging
|
||||
|
||||
```python
|
||||
def run_test(runtime_ms):
|
||||
# Take screenshot before action
|
||||
automation.screenshot("before.png")
|
||||
|
||||
# Perform action
|
||||
do_something()
|
||||
|
||||
# Take screenshot after
|
||||
automation.screenshot("after.png")
|
||||
|
||||
# Now you can compare visually
|
||||
```
|
||||
|
||||
### Print Debugging
|
||||
|
||||
```python
|
||||
def test_complex_operation():
|
||||
print("Starting test...")
|
||||
|
||||
obj = create_object()
|
||||
print(f"Created: {obj}")
|
||||
|
||||
result = obj.process()
|
||||
print(f"Result: {result}")
|
||||
|
||||
assert result == expected
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- CLAUDE.md - Testing guidelines section
|
||||
- [[Performance-Optimization-Workflow]] - Creating benchmarks
|
||||
- `tests/` directory - Example tests
|
||||
|
||||
**Testing Pattern Examples:**
|
||||
- `tests/test_grid_operations.py` - Direct execution
|
||||
- `tests/issue_78_middle_click_test.py` - Timer-based with automation
|
||||
Loading…
Add table
Add a link
Reference in a new issue