# Save System Testing Guide ## Overview Save system testing ensures data persistence, integrity, and compatibility across game versions. Save bugs are among the most frustrating for players—data loss destroys trust. ## Test Categories ### Data Integrity | Test Type | Description | Priority | | -------------------- | ------------------------------------------- | -------- | | Round-trip | Save → Load → Verify all data matches | P0 | | Corruption detection | Tampered/corrupted files handled gracefully | P0 | | Partial write | Power loss during save doesn't corrupt | P0 | | Large saves | Performance with max-size save files | P1 | | Edge values | Min/max values for all saved fields | P1 | ### Version Compatibility | Scenario | Expected Behavior | | ----------------------- | ------------------------------------- | | Current → Current | Full compatibility | | Old → New (upgrade) | Migration with data preservation | | New → Old (downgrade) | Graceful rejection or limited support | | Corrupted version field | Fallback to recovery mode | ## Test Scenarios ### Core Save/Load Tests ``` SCENARIO: Basic Save Round-Trip GIVEN player has 100 health, 50 gold, position (10, 5, 20) AND player has inventory: ["sword", "potion", "key"] WHEN game is saved AND game is reloaded THEN player health equals 100 AND player gold equals 50 AND player position equals (10, 5, 20) AND inventory contains exactly ["sword", "potion", "key"] SCENARIO: Save During Gameplay GIVEN player is in combat AND enemy has 50% health remaining WHEN autosave triggers AND game is reloaded THEN combat state is restored AND enemy health equals 50% SCENARIO: Multiple Save Slots GIVEN save slot 1 has character "Hero" at level 10 AND save slot 2 has character "Mage" at level 5 WHEN switching between slots THEN correct character data loads for each slot AND no cross-contamination between slots ``` ### Edge Cases ``` SCENARIO: Maximum Inventory Save GIVEN player has 999 items in inventory WHEN game is saved AND game is reloaded THEN all 999 items are preserved AND save/load completes within 5 seconds SCENARIO: Unicode Character Names GIVEN player name is "プレイヤー名" WHEN game is saved AND game is reloaded THEN player name displays correctly SCENARIO: Extreme Play Time GIVEN play time is 9999:59:59 WHEN game is saved AND game is reloaded THEN play time displays correctly AND timer continues from saved value ``` ### Corruption Recovery ``` SCENARIO: Corrupted Save Detection GIVEN save file has been manually corrupted WHEN game attempts to load THEN error is detected before loading AND user is informed of corruption AND game does not crash SCENARIO: Missing Save File GIVEN save file has been deleted externally WHEN game attempts to load THEN graceful error handling AND option to start new game or restore backup SCENARIO: Interrupted Save (Power Loss) GIVEN save operation is interrupted mid-write WHEN game restarts THEN backup save is detected and offered AND no data loss from previous valid save ``` ## Platform-Specific Testing ### PC (Steam/Epic) - Cloud save sync conflicts - Multiple Steam accounts on same PC - Offline → Online sync - Save location permissions (Program Files issues) ### Console (PlayStation/Xbox/Switch) - System-level save management - Storage full scenarios - User switching mid-game - Suspend/resume with unsaved changes - Cloud save quota limits ### Mobile - App termination during save - Low storage warnings - iCloud/Google Play sync - Device migration ## Automated Test Examples ### Unity ```csharp [Test] public void SaveLoad_PlayerStats_PreservesAllValues() { var original = new PlayerData { Health = 75, MaxHealth = 100, Gold = 1234567, Position = new Vector3(100.5f, 0, -50.25f), PlayTime = 36000f // 10 hours }; SaveManager.Save(original, "test_slot"); var loaded = SaveManager.Load("test_slot"); Assert.AreEqual(original.Health, loaded.Health); Assert.AreEqual(original.Gold, loaded.Gold); Assert.AreEqual(original.Position, loaded.Position); Assert.AreEqual(original.PlayTime, loaded.PlayTime, 0.01f); } [Test] public void SaveLoad_CorruptedFile_HandlesGracefully() { File.WriteAllText(SaveManager.GetPath("corrupt"), "INVALID DATA"); Assert.Throws(() => SaveManager.Load("corrupt")); // Game should not crash Assert.IsTrue(SaveManager.IsValidSaveSlot("corrupt") == false); } ``` ### Unreal ```cpp bool FSaveSystemTest::RunTest(const FString& Parameters) { // Create test save USaveGame* SaveGame = UGameplayStatics::CreateSaveGameObject( UMySaveGame::StaticClass()); UMySaveGame* MySave = Cast(SaveGame); MySave->PlayerLevel = 50; MySave->Gold = 999999; MySave->QuestsCompleted = {"Quest1", "Quest2", "Quest3"}; // Save UGameplayStatics::SaveGameToSlot(MySave, "TestSlot", 0); // Load USaveGame* Loaded = UGameplayStatics::LoadGameFromSlot("TestSlot", 0); UMySaveGame* LoadedSave = Cast(Loaded); TestEqual("Level preserved", LoadedSave->PlayerLevel, 50); TestEqual("Gold preserved", LoadedSave->Gold, 999999); TestEqual("Quests count", LoadedSave->QuestsCompleted.Num(), 3); return true; } ``` ### Godot ```gdscript func test_save_load_round_trip(): var original = { "health": 100, "position": Vector3(10, 0, 20), "inventory": ["sword", "shield"], "quest_flags": {"intro_complete": true, "boss_defeated": false} } SaveManager.save_game(original, "test_save") var loaded = SaveManager.load_game("test_save") assert_eq(loaded.health, 100) assert_eq(loaded.position, Vector3(10, 0, 20)) assert_eq(loaded.inventory.size(), 2) assert_true(loaded.quest_flags.intro_complete) assert_false(loaded.quest_flags.boss_defeated) func test_corrupted_save_detection(): var file = FileAccess.open("user://saves/corrupt.sav", FileAccess.WRITE) file.store_string("CORRUPTED GARBAGE DATA") file.close() var result = SaveManager.load_game("corrupt") assert_null(result, "Should return null for corrupted save") assert_false(SaveManager.is_valid_save("corrupt")) ``` ## Migration Testing ### Version Upgrade Matrix | From Version | To Version | Test Focus | | -------------- | ---------------- | ---------------------------- | | 1.0 → 1.1 | Minor update | New fields default correctly | | 1.x → 2.0 | Major update | Schema migration works | | Beta → Release | Launch migration | All beta saves convert | ### Migration Test Template ``` SCENARIO: Save Migration v1.0 to v2.0 GIVEN save file from version 1.0 AND save contains old inventory format (array) WHEN game version 2.0 loads the save THEN inventory is migrated to new format (dictionary) AND all items are preserved AND migration is logged AND backup of original is created ``` ## Performance Benchmarks | Metric | Target | Maximum | | ------------------------ | --------------- | ------- | | Save time (typical) | < 500ms | 2s | | Save time (large) | < 2s | 5s | | Load time (typical) | < 1s | 3s | | Save file size (typical) | < 1MB | 10MB | | Memory during save | < 50MB overhead | 100MB | ## Best Practices ### DO - Use atomic saves (write to temp, then rename) - Keep backup of previous save - Version your save format - Encrypt sensitive data - Test on minimum-spec hardware - Compress large saves ### DON'T - Store absolute file paths - Save derived/calculated data - Trust save file contents blindly - Block gameplay during save - Forget to handle storage-full scenarios - Skip testing save migration paths