Files
MusicPlayer/TESTING.md
T
Sansan ab338b0696 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.
2026-10-06 08:32:55 +02:00

9.3 KiB

MusicPlayer Testing Guide

This document describes the testing setup for the MusicPlayer project using GdUnit4.

Testing Framework

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)

# 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:

  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

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

  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

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.