- Add GdUnit4 v6.2.1 framework in addons/gdUnit4/ - Create test directory structure (tests/unit/, tests/integration/, tests/mocks/) - Add unit tests for MusicPlayer.gd (8 tests) - Add unit tests for MainWindow.gd (15+ tests) - Add unit tests for Settings.gd (5 tests) - Add unit tests for Playlist.gd (11 tests) - Add integration tests for full flow (10+ tests) - Add MockHTTPRequest for HTTP request mocking - Add TestHelpers with utility functions - Add Gitea Actions CI workflow (.gitea/workflows/test.yml) - Add .runsettings for GdUnit4 CLI configuration - Add run_tests.sh for convenient test execution - Add TESTING.md with comprehensive documentation - Add TESTING_PLAN.md with implementation details - Update .gitignore for test artifacts - Enable GdUnit4 plugin in project.godot Features: - Test button functionality (play, pause, search, sync, etc.) - Mock REST API requests to server - Test complete application flows - Run tests in CI (headless mode) - Run tests in GUI for visual feedback - JUnit XML and HTML test reports Generated by OpenCode AI assistant.
9.3 KiB
9.3 KiB
MusicPlayer Testing Guide
This document describes the testing setup for the MusicPlayer project using GdUnit4.
Testing Framework
- Framework: GdUnit4 v6.2.1
- Godot Version: 4.7+
- Language: GDScript
Directory Structure
MusicPlayer/
├── addons/gdUnit4/ # GdUnit4 framework
├── tests/
│ ├── unit/ # Unit tests
│ │ ├── test_basic.gd # Basic sanity tests
│ │ ├── test_music_player.gd # MusicPlayer.gd tests
│ │ ├── test_main_window.gd # MainWindow.gd tests
│ │ ├── test_settings.gd # Settings.gd tests
│ │ └── test_playlist.gd # Playlist.gd tests
│ ├── integration/ # Integration tests
│ │ └── test_full_flow.gd # End-to-end flow tests
│ └── mocks/ # Mock utilities
│ ├── mock_http_request.gd # HTTP request mocking
│ └── test_helpers.gd # Test helper functions
├── .gitea/workflows/test.yml # Gitea Actions CI
├── .runsettings # GdUnit4 CLI settings
├── run_tests.sh # Test runner script
└── TESTING.md # This file
Running Tests
In Godot Editor
- Open the project in Godot 4.7
- Enable the GdUnit4 plugin in Project > Project Settings > Plugins
- Open the GdUnit4 Inspector panel (should appear automatically)
- Run tests by:
- Right-clicking on test files in the FileSystem panel
- Using the GdUnit4 Inspector panel
- Right-clicking in the Script Editor
From Command Line (Headless)
# Make the test runner executable (one-time setup)
chmod +x run_tests.sh
# Run all tests in headless mode
./run_tests.sh --headless
# Run with verbose output
./run_tests.sh --headless --verbose
# Or directly with Godot
godot --path . -s addons/gdUnit4/test.gd --gdunit.cli --headless
Direct Godot Command
# Run all tests
godot --path /path/to/MusicPlayer -s addons/gdUnit4/test.gd --gdunit.cli
# Run specific test suite
godot --path . -s addons/gdUnit4/test.gd --gdunit.cli --gdunit.filter=MusicPlayerTest
Test Coverage
Unit Tests
| Test File | Component | Coverage |
|---|---|---|
test_music_player.gd |
MusicPlayer.gd | Play/Pause, Restart, Seek, Volume |
test_main_window.gd |
MainWindow.gd | Buttons, Search, Player List |
test_settings.gd |
Settings.gd | HTTP Requests, Utilities |
test_playlist.gd |
Playlist.gd | Song Management, Points |
test_full_flow.gd |
Integration | Fetch → Play → Next Flow |
Tested Functionality
MusicPlayer.gd
- ✅ Play/Pause button state toggling
- ✅ Restart button functionality
- ✅ Pause button functionality
- ✅ Time formatting (format_time)
- ✅ Text formatting with hide options (format_text)
- ✅ Drag started/ended on progress slider
- ✅ Seek functionality
- ✅ Volume change
MainWindow.gd
- ✅ All button signal connections
- ✅ Add players functionality
- ✅ Show/hide search view
- ✅ Show/hide settings window
- ✅ Show QR code window
- ✅ Show about window
- ✅ Get statistics
- ✅ Sync soundtracks
- ✅ Reset playlist
- ✅ Reset points
- ✅ Play next song
- ✅ Song object clicked (with download)
- ✅ Player removed
- ✅ Point given/taken
Settings.gd
- ✅ HTTP request mocking (make_request2, make_request3)
- ✅ Delete children utilities
Playlist.gd
- ✅ Add to playlist
- ✅ Get next song
- ✅ Set latest played song
- ✅ Get preloaded songs count
- ✅ Should preload more songs
- ✅ Song is answered
- ✅ Add/remove points
- ✅ Get song stream
- ✅ Reset playlist
- ✅ Clear song list
Integration Tests
- ✅ Fetch song list at start
- ✅ Fetch song flow (rand → info → addQue)
- ✅ Search function
- ✅ Player list updates
- ✅ Point system
- ✅ Winner declaration
- ✅ Show answer flow
- ✅ Update song list
Mocking
HTTP Request Mocking
The MockHTTPRequest class in tests/mocks/mock_http_request.gd allows you to mock HTTP responses:
var mock_http = MockHTTPRequest.new()
mock_http.add_mock_response("https://api.example.com/data", 200, '{"key": "value"}')
Settings Mocking
For testing HTTP requests, temporarily replace Settings.make_request2:
var original_make_request = Settings.make_request2
Settings.make_request2 = func(address: String, func_name: Callable, expect_data: bool):
func_name.call({"SongNo": 1, "Soundtrack": "Test", "Song": "Test Song"})
# Run your test
# Restore original
Settings.make_request2 = original_make_request
Test Helpers
The TestHelpers class provides utility functions:
# Create a mock audio stream
var stream = TestHelpers.create_mock_audio_stream()
# Create a test song object
var song = TestHelpers.create_test_song_object(1, "Test Soundtrack", "Test Song")
# Setup test environment
TestHelpers.setup_test_environment()
# Cleanup test environment
TestHelpers.cleanup_test_environment()
Continuous Integration
Gitea Actions
The project includes a Gitea Actions workflow (.gitea/workflows/test.yml) that:
- Checks out the repository
- Sets up Godot 4.7
- Installs GdUnit4 (if not present)
- Runs all tests in headless mode
- Uploads test results as artifacts
- Fails the build if tests fail
Test Results
After running tests, the following files are generated:
test_results.xml- JUnit XML format (for CI systems)test_results.html- HTML report (for human reading)
Writing New Tests
Basic Test Structure
class_name MyNewTest
extends GdUnitTestSuite
func test_something():
# Arrange
var value = 42
# Act
var result = value * 2
# Assert
assert_eq(result, 84)
Testing with Mocks
func test_with_mock():
var mock_object = mock(Node)
# Verify a method was called
mock_object.some_method()
assert_true(mock_object.some_method.was_called())
# Verify a method was called with specific arguments
mock_object.some_method("arg1", 42)
assert_true(mock_object.some_method.was_called_with(["arg1", 42]))
Async Testing
func test_async_operation():
var completed = false
var callback = func():
completed = true
# Start async operation
some_async_function(callback)
# Wait for completion
await get_tree().create_timer(0.1).timeout
assert_true(completed)
Assertions
GdUnit4 provides a rich set of assertions:
Basic Assertions
assert_true(condition)assert_false(condition)assert_eq(actual, expected)assert_ne(actual, expected)assert_null(value)assert_not_null(value)
Type Assertions
assert_is_type(value, TYPE_INT)assert_is_not_type(value, TYPE_STRING)
String Assertions
assert_str("hello").is_equal_to("hello")assert_str("hello").has_length(5)assert_str("hello").starts_with("he")assert_str("hello").ends_with("lo")assert_str("hello").contains("ell")
Array Assertions
assert_arr([1, 2, 3]).has_size(3)assert_arr([1, 2, 3]).contains(2)assert_arr([1, 2, 3]).does_not_contain(4)
Dictionary Assertions
assert_dict({"a": 1}).has_size(1)assert_dict({"a": 1}).contains_key("a")assert_dict({"a": 1}).contains_value(1)
Number Assertions
assert_num(42).is_equal_to(42)assert_num(42).is_greater_than(40)assert_num(42).is_less_than(50)assert_num(42).is_between(40, 50)
File Assertions
assert_file("res://test.txt").exists()assert_file("res://test.txt").has_size(1024)
Signal Assertions
assert_signal(signal).was_emitted()assert_signal(signal).was_emitted_with([arg1, arg2])
Best Practices
- Test Isolation: Each test should be independent of others
- Setup and Teardown: Use
_setup()and_teardown()methods for test lifecycle - Mock External Dependencies: Mock HTTP requests, file I/O, etc.
- Test One Thing: Each test should verify one specific behavior
- Use Descriptive Names: Test method names should describe what they test
- Keep Tests Fast: Tests should run quickly for CI
- Test Edge Cases: Include tests for error conditions and boundary values
Troubleshooting
Tests not discovered
- Ensure test classes extend
GdUnitTestSuite - Ensure test methods start with
test_ - Check that the GdUnit4 plugin is enabled
Tests fail in CI but pass locally
- Check Godot version compatibility
- Ensure headless mode works for your tests
- Verify file paths are correct (use
res://paths)
Mocking not working
- Ensure you're using the GdUnit4
mock()function - Verify method names are spelled correctly
- Check that signals are connected properly
Resources
Contributing
When adding new features:
- Add corresponding unit tests
- Update integration tests if needed
- Ensure all tests pass before submitting a PR
- Add test coverage for edge cases
License
The testing framework and tests are part of the MusicPlayer project and share the same license.