Add GdUnit4 testing framework with comprehensive test coverage

- 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.
This commit is contained in:
Sansan committed 2026-10-06 08:32:55 +02:00
1 parent 9a91d969bd
commit ab338b0696
15 files changed
+2146 -1

No files matched your search

+343
View File
@@ -0,0 +1,343 @@
# MusicPlayer Testing Guide
This document describes the testing setup for the MusicPlayer project using **GdUnit4**.
## Testing Framework
- **Framework**: [GdUnit4 v6.2.1](https://github.com/godot-gdunit-labs/gdUnit4)
- **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
1. Open the project in Godot 4.7
2. Enable the GdUnit4 plugin in **Project > Project Settings > Plugins**
3. Open the **GdUnit4 Inspector** panel (should appear automatically)
4. 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)
```bash
# 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
```bash
# 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:
```gdscript
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`:
```gdscript
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:
```gdscript
# 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:
1. Checks out the repository
2. Sets up Godot 4.7
3. Installs GdUnit4 (if not present)
4. Runs all tests in headless mode
5. Uploads test results as artifacts
6. 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
```gdscript
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
```gdscript
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
```gdscript
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
1. **Test Isolation**: Each test should be independent of others
2. **Setup and Teardown**: Use `_setup()` and `_teardown()` methods for test lifecycle
3. **Mock External Dependencies**: Mock HTTP requests, file I/O, etc.
4. **Test One Thing**: Each test should verify one specific behavior
5. **Use Descriptive Names**: Test method names should describe what they test
6. **Keep Tests Fast**: Tests should run quickly for CI
7. **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
- [GdUnit4 Documentation](https://godot-gdunit-labs.github.io/gdUnit4/latest/)
- [GdUnit4 GitHub](https://github.com/godot-gdunit-labs/gdUnit4)
- [Godot Documentation](https://docs.godotengine.org/)
## Contributing
When adding new features:
1. Add corresponding unit tests
2. Update integration tests if needed
3. Ensure all tests pass before submitting a PR
4. Add test coverage for edge cases
## License
The testing framework and tests are part of the MusicPlayer project and share the same license.