#nullable enable using Newtonsoft.Json.Linq; using System; using System.Linq; using UnityEngine; using UVC.Data.Core; namespace UVC.Sample.Data { /// /// DataObject 기능 샘플 코드입니다. /// DataSampleRunner Inspector에서 실행하거나 Play 버튼으로 확인합니다. /// public class DataObjectTests { private DataObject _dataObject = new DataObject(); /// /// 모든 샘플을 순차 실행합니다. /// public void RunAll() { Debug.Log("===== DataObject 샘플 시작 ====="); SetUp(); Run(nameof(UpdateDifferent_WhenCalled_UpdatesAndTracksChanges), UpdateDifferent_WhenCalled_UpdatesAndTracksChanges); SetUp(); Run(nameof(Indexer_WhenPropertyIsSet_TracksChange), Indexer_WhenPropertyIsSet_TracksChange); SetUp(); Run(nameof(Indexer_WhenNewPropertyIsAdded_TracksAsChange), Indexer_WhenNewPropertyIsAdded_TracksAsChange); SetUp(); Run(nameof(GetUpdatedObject_WhenCalledAfterChanges_ReturnsOnlyUpdatedProperties), GetUpdatedObject_WhenCalledAfterChanges_ReturnsOnlyUpdatedProperties); SetUp(); Run(nameof(MarkAllAsUpdated_WhenCalled_MarksAllPropertiesAsChanged), MarkAllAsUpdated_WhenCalled_MarksAllPropertiesAsChanged); SetUp(); Run(nameof(Remove_WhenCalled_RemovesPropertyAndChangeTracking), Remove_WhenCalled_RemovesPropertyAndChangeTracking); SetUp(); Run(nameof(GetTypeMethods_ReturnCorrectTypes), GetTypeMethods_ReturnCorrectTypes); SetUp(); Run(nameof(Constructor_FromJObject_CreatesCorrectObject), Constructor_FromJObject_CreatesCorrectObject); SetUp(); Run(nameof(IdProperty_ReturnsCorrectId), IdProperty_ReturnsCorrectId); SetUp(); Run(nameof(KeyOrder_IsPreservedByInsertion), KeyOrder_IsPreservedByInsertion); SetUp(); Run(nameof(DataObjectPool_GetAndReturn_WorksCorrectly), DataObjectPool_GetAndReturn_WorksCorrectly); Debug.Log("===== DataObject 샘플 완료 ====="); } private void SetUp() { _dataObject = new DataObject { { "Id", 1 }, { "name", "Test" }, { "value", 100.5f }, { "isActive", true } }; _dataObject.ClearChangedProperties(); } private void Run(string name, Action action) { try { action(); Debug.Log($"[OK] {name}"); } catch (Exception ex) { Debug.LogError($"[FAIL] {name}\n{ex.Message}"); } } private static void Check(bool condition, string message) { if (!condition) throw new Exception($"검증 실패: {message}"); } /// /// UpdateDifferent: 변경된 프로퍼티만 추적해서 업데이트합니다. /// public void UpdateDifferent_WhenCalled_UpdatesAndTracksChanges() { var otherDataObject = new DataObject { { "Id", 1 }, { "name", "Updated Test" }, { "newValue", "new" } }; _dataObject.UpdateDifferent(otherDataObject, true); Check((string)_dataObject["name"] == "Updated Test", "name이 'Updated Test'여야 합니다."); Check((string)_dataObject["newValue"] == "new", "newValue가 'new'여야 합니다."); Check(_dataObject.UpdatedCount == 2, "UpdatedCount는 2여야 합니다."); Check(_dataObject.ChangedProperies.Contains("name"), "ChangedProperies에 'name'이 포함되어야 합니다."); Check(_dataObject.ChangedProperies.Contains("newValue"), "ChangedProperies에 'newValue'가 포함되어야 합니다."); } /// /// Indexer: 기존 프로퍼티 변경 시 변경 추적됩니다. /// public void Indexer_WhenPropertyIsSet_TracksChange() { _dataObject["name"] = "New Name"; Check(_dataObject.UpdatedCount == 1, "UpdatedCount는 1이어야 합니다."); Check(_dataObject.ChangedProperies.Contains("name"), "ChangedProperies에 'name'이 포함되어야 합니다."); } /// /// Indexer: 새 프로퍼티 추가 시 변경으로 추적됩니다. /// public void Indexer_WhenNewPropertyIsAdded_TracksAsChange() { _dataObject["newKey"] = "newValue"; Check(_dataObject.UpdatedCount == 1, "새로 추가된 속성은 변경으로 간주되어야 합니다."); Check(_dataObject.ChangedProperies.Contains("newKey"), "ChangedProperies에 'newKey'가 포함되어야 합니다."); } /// /// GetUpdatedObject: 변경된 프로퍼티를 반환합니다. /// IdKey 미설정 시 첫 번째 키("Id")가 자동으로 포함되므로 최소 Count는 변경 수 + 1입니다. /// public void GetUpdatedObject_WhenCalledAfterChanges_ReturnsOnlyUpdatedProperties() { _dataObject["name"] = "Updated Name"; _dataObject["value"] = 200.0f; var updatedObject = _dataObject.GetUpdatedObject() as DataObject; Check(updatedObject != null, "updatedObject가 null이 아니어야 합니다."); // IdKey 미설정 시 첫 번째 키("Id")가 자동 포함 → 변경 2개 + Id 1개 = 3개 Check(updatedObject!.Count == 3, "변경된 프로퍼티 2개 + 자동 포함 Id 1개 = 3개여야 합니다."); Check((string)updatedObject["name"] == "Updated Name", "name이 'Updated Name'이어야 합니다."); Check(updatedObject.ContainsKey("value"), "value 키가 포함되어야 합니다."); // IdKey 없으면 첫 번째 키(Id)가 자동 포함됩니다. Check(updatedObject.ContainsKey("Id"), "IdKey 미설정 시 첫 번째 키 'Id'가 자동 포함되어야 합니다."); } /// /// MarkAllAsUpdated: 모든 프로퍼티를 변경 상태로 표시합니다. /// public void MarkAllAsUpdated_WhenCalled_MarksAllPropertiesAsChanged() { _dataObject.MarkAllAsUpdated(); Check(_dataObject.UpdatedCount == _dataObject.Count, "UpdatedCount는 전체 프로퍼티 수와 같아야 합니다."); foreach (var key in _dataObject.Keys) Check(_dataObject.ChangedProperies.Contains(key), $"ChangedProperies에 '{key}'가 포함되어야 합니다."); } /// /// Remove: 프로퍼티 제거 시 변경 추적도 함께 제거됩니다. /// public void Remove_WhenCalled_RemovesPropertyAndChangeTracking() { _dataObject["name"] = "A new name to be removed"; Check(_dataObject.ChangedProperies.Contains("name"), "사전 조건: 'name'이 변경 목록에 있어야 합니다."); bool result = _dataObject.Remove("name"); Check(result, "Remove는 true를 반환해야 합니다."); Check(!_dataObject.ContainsKey("name"), "'name' 키가 제거되어야 합니다."); Check(!_dataObject.ChangedProperies.Contains("name"), "변경 추적에서도 'name'이 제거되어야 합니다."); } /// /// 타입별 Get 메서드가 올바른 타입을 반환합니다. /// public void GetTypeMethods_ReturnCorrectTypes() { Check(_dataObject.GetInt("Id") == 1, "GetInt('Id')는 1이어야 합니다."); Check(_dataObject.GetString("name") == "Test", "GetString('name')은 'Test'여야 합니다."); Check(_dataObject.GetFloat("value") == 100.5f, "GetFloat('value')는 100.5f여야 합니다."); Check(_dataObject.GetBool("isActive") == true, "GetBool('isActive')는 true여야 합니다."); } /// /// JObject 생성자로 DataObject를 만들 수 있습니다. /// public void Constructor_FromJObject_CreatesCorrectObject() { var jObject = new JObject { { "Id", 10 }, { "user", "jobject_user" } }; var dataObject = new DataObject(jObject); Check(dataObject.GetInt("Id") == 10, "GetInt('Id')는 10이어야 합니다."); Check(dataObject.GetString("user") == "jobject_user", "GetString('user')는 'jobject_user'여야 합니다."); Check(dataObject.UpdatedCount == 0, "생성자 호출 시 변경 추적을 하지 않아야 합니다."); } /// /// IdKey 설정에 따라 Id 프로퍼티가 올바르게 동작합니다. /// public void IdProperty_ReturnsCorrectId() { _dataObject.IdKey = "Id"; Check(_dataObject.Id == "1", "IdKey='Id' 일 때 Id는 '1'이어야 합니다."); var noIdKeyObject = new DataObject { { "name", "Test" }, { "value", 100 } }; Check(noIdKeyObject.Id == "Test", "IdKey 미설정 시 첫 번째 값이 Id여야 합니다."); } /// /// 삽입 순서대로 Key 순서가 유지됩니다. /// public void KeyOrder_IsPreservedByInsertion() { var orderedDataObject = new DataObject(); orderedDataObject.Add("first", 1); orderedDataObject.Add("second", 2); orderedDataObject.Add("third", 3); var keys = orderedDataObject.Keys.ToList(); Check(keys[0] == "first", "첫 번째 키는 'first'여야 합니다."); Check(keys[1] == "second", "두 번째 키는 'second'여야 합니다."); Check(keys[2] == "third", "세 번째 키는 'third'여야 합니다."); } /// /// DataObjectPool: 풀에서 가져오고 반환하는 기능을 확인합니다. /// public void DataObjectPool_GetAndReturn_WorksCorrectly() { var obj = DataObjectPool.Get(); obj["test"] = "value"; Check(obj != null, "풀에서 가져온 객체는 null이 아니어야 합니다."); Check(obj.Count == 1, "객체에 1개의 항목이 있어야 합니다."); DataObjectPool.Return(obj); var retrievedObj = DataObjectPool.Get(); Check(retrievedObj.Count == 0, "풀에서 다시 가져온 객체는 비어있어야 합니다."); } } }