490 lines
18 KiB
C#
490 lines
18 KiB
C#
#nullable enable
|
|
|
|
using Cysharp.Threading.Tasks;
|
|
using Newtonsoft.Json.Linq;
|
|
using NUnit.Framework;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Threading.Tasks;
|
|
using UnityEngine;
|
|
using UVC.Data;
|
|
using UVC.Network;
|
|
|
|
namespace UVC.Tests.Data
|
|
{
|
|
/// <summary>
|
|
/// HttpPipeLine 클래스의 테스트를 위한 테스트 클래스입니다.
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class HttpPipeLineTests
|
|
{
|
|
// 테스트에 사용할 HttpPipeLine 인스턴스
|
|
private HttpPipeLine pipeLine;
|
|
|
|
/// <summary>
|
|
/// 각 테스트 실행 전에 호출되는 설정 메서드입니다.
|
|
/// </summary>
|
|
[SetUp]
|
|
public void Setup()
|
|
{
|
|
pipeLine = new HttpPipeLine();
|
|
// 테스트를 위한 DataRepository 초기화
|
|
ClearDataRepository();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 테스트 메서드를 실행하는 메서드입니다.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// 이 메서드는 클래스의 모든 테스트 메서드를 순차적으로 호출하고
|
|
/// 각 테스트의 성공 또는 실패 여부를 로그로 출력합니다.
|
|
/// </remarks>
|
|
public void TestAll()
|
|
{
|
|
Setup();
|
|
|
|
Debug.Log("===== HttpPipeLine 테스트 시작 =====");
|
|
|
|
RunTest(nameof(Add_NewInfo_AddedSuccessfully), Add_NewInfo_AddedSuccessfully);
|
|
RunTest(nameof(Add_ExistingInfo_UpdatesExistingEntry), Add_ExistingInfo_UpdatesExistingEntry);
|
|
RunTest(nameof(Remove_ExistingInfo_RemovedSuccessfully), Remove_ExistingInfo_RemovedSuccessfully);
|
|
RunTest(nameof(Remove_NonExistingInfo_DoesNothing), Remove_NonExistingInfo_DoesNothing);
|
|
RunTest(nameof(Excute_WithNonExistingKey_DoesNothing), Excute_WithNonExistingKey_DoesNothing);
|
|
// 비동기 테스트는 RunTest로 실행하기 어려워 별도 처리 필요
|
|
// 간단하게 메시지만 출력
|
|
Debug.Log($"비동기 테스트: {nameof(Excute_WithJObjectResponse_ProcessesDataCorrectly)}와 {nameof(Excute_WithJArrayResponse_ProcessesDataCorrectly)}는 수동으로 실행하세요.");
|
|
Debug.Log("===== HttpPipeLine 테스트 완료 =====");
|
|
}
|
|
|
|
/// <summary>
|
|
/// 단일 테스트 메서드를 실행하고 결과를 로그로 출력합니다.
|
|
/// </summary>
|
|
/// <param name="testName">테스트 메서드 이름</param>
|
|
/// <param name="testAction">실행할 테스트 메서드</param>
|
|
private void RunTest(string testName, Action testAction)
|
|
{
|
|
try
|
|
{
|
|
Debug.Log($"테스트 시작: {testName}");
|
|
testAction();
|
|
Debug.Log($"테스트 성공: {testName}");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// 새로운 HttpPipeLineInfo를 추가하는 테스트
|
|
/// </summary>
|
|
[Test]
|
|
public void Add_NewInfo_AddedSuccessfully()
|
|
{
|
|
// Arrange
|
|
var info = new HttpPipeLineInfo("http://test.com");
|
|
|
|
// Act
|
|
pipeLine.Add("test", info);
|
|
|
|
// Assert - Dictionary에 추가되었는지 확인 (reflection 사용)
|
|
var infoList = GetInfoListField();
|
|
Assert.IsTrue(infoList.ContainsKey("test"));
|
|
Assert.AreEqual(info, infoList["test"]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 기존에 존재하는 키로 HttpPipeLineInfo를 추가할 때 업데이트 테스트
|
|
/// </summary>
|
|
[Test]
|
|
public void Add_ExistingInfo_UpdatesExistingEntry()
|
|
{
|
|
// Arrange
|
|
var info1 = new HttpPipeLineInfo("http://test1.com");
|
|
var info2 = new HttpPipeLineInfo("http://test2.com");
|
|
pipeLine.Add("test", info1);
|
|
|
|
// Act
|
|
pipeLine.Add("test", info2);
|
|
|
|
// Assert
|
|
var infoList = GetInfoListField();
|
|
Assert.IsTrue(infoList.ContainsKey("test"));
|
|
Assert.AreEqual(info2, infoList["test"]);
|
|
Assert.AreNotEqual(info1, infoList["test"]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 존재하는 HttpPipeLineInfo를 제거하는 테스트
|
|
/// </summary>
|
|
[Test]
|
|
public void Remove_ExistingInfo_RemovedSuccessfully()
|
|
{
|
|
// Arrange
|
|
var info = new HttpPipeLineInfo("http://test.com");
|
|
pipeLine.Add("test", info);
|
|
|
|
// Act
|
|
pipeLine.Remove("test");
|
|
|
|
// Assert
|
|
var infoList = GetInfoListField();
|
|
Assert.IsFalse(infoList.ContainsKey("test"));
|
|
}
|
|
|
|
/// <summary>
|
|
/// 존재하지 않는 키에 대한 Remove 호출 테스트
|
|
/// </summary>
|
|
[Test]
|
|
public void Remove_NonExistingInfo_DoesNothing()
|
|
{
|
|
// Arrange
|
|
var info = new HttpPipeLineInfo("http://test.com");
|
|
pipeLine.Add("test", info);
|
|
|
|
// Act - 존재하지 않는 키 제거 시도
|
|
pipeLine.Remove("nonexistent");
|
|
|
|
// Assert - 기존 항목은 여전히 존재해야 함
|
|
var infoList = GetInfoListField();
|
|
Assert.IsTrue(infoList.ContainsKey("test"));
|
|
Assert.AreEqual(info, infoList["test"]);
|
|
}
|
|
|
|
/// <summary>
|
|
/// HttpPipeLine의 private infoList 필드 가져오기
|
|
/// </summary>
|
|
private Dictionary<string, HttpPipeLineInfo> GetInfoListField()
|
|
{
|
|
var fieldInfo = typeof(HttpPipeLine).GetField("infoList",
|
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
return (Dictionary<string, HttpPipeLineInfo>)fieldInfo.GetValue(pipeLine);
|
|
}
|
|
|
|
/// <summary>
|
|
/// DataRepository를 테스트를 위해 초기화 (리플렉션 사용)
|
|
/// </summary>
|
|
private void ClearDataRepository()
|
|
{
|
|
var repositoryType = typeof(DataRepository);
|
|
var instanceField = repositoryType.GetField("instance",
|
|
BindingFlags.NonPublic | BindingFlags.Static);
|
|
|
|
if (instanceField != null)
|
|
{
|
|
// Lazy<DataRepository> 필드 값을 가져옴
|
|
var lazyInstance = instanceField.GetValue(null);
|
|
|
|
// Lazy<T>의 Value 속성을 통해 인스턴스 접근
|
|
var instance = lazyInstance.GetType().GetProperty("Value").GetValue(lazyInstance);
|
|
|
|
// dataObjects 딕셔너리 필드 가져오기
|
|
var dataObjectsField = repositoryType.GetField("dataObjects",
|
|
BindingFlags.NonPublic | BindingFlags.Instance);
|
|
|
|
// 딕셔너리 접근하여 Clear 메서드 호출
|
|
var dataObjects = dataObjectsField.GetValue(instance) as Dictionary<string, IDataObject>;
|
|
dataObjects.Clear();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Excute 메소드에서 JObject 응답을 처리하는 기능 테스트
|
|
/// </summary>
|
|
[Test]
|
|
public async Task Excute_WithJObjectResponse_ProcessesDataCorrectly()
|
|
{
|
|
// Arrange
|
|
bool handlerCalled = false;
|
|
DataObject? receivedData = null;
|
|
|
|
var mockResponse = @"{""name"": ""테스트"", ""value"": 123}";
|
|
|
|
// DataMask와 DataMapper 설정
|
|
var dataMask = new DataMask();
|
|
dataMask["name"] = "이름";
|
|
dataMask["value"] = 0;
|
|
|
|
var dataMapper = new DataMapper(dataMask);
|
|
|
|
// HttpPipeLineInfo 설정
|
|
var info = new HttpPipeLineInfo("http://test.com")
|
|
.setDataMapper(dataMapper)
|
|
.setHandler((data) =>
|
|
{
|
|
handlerCalled = true;
|
|
if (data is DataObject dataObject)
|
|
{
|
|
receivedData = dataObject;
|
|
}
|
|
});
|
|
|
|
pipeLine.Add("testKey", info);
|
|
|
|
try
|
|
{
|
|
// HttpRequester의 Request 메소드를 대체하는 대신
|
|
// 메소드를 직접 호출하여 테스트에서 통제할 수 있게 함
|
|
// (실제 구현은 Mock 프레임워크를 사용하는 것이 좋음)
|
|
|
|
// Execute 메소드 구현은 비동기지만 async void라서 직접 테스트 불가능
|
|
// 대신 private 필드에 접근하여 로직을 검증
|
|
|
|
// Act
|
|
// 이 시점에서 실제 HTTP 요청 대신 mockResponse를 반환하도록 설정해야 함
|
|
// 아래는 conceptual pseudo-code (실제 구현 불가)
|
|
// MockHttpRequesterHelper.PatchHttpRequester(
|
|
// (url, method, body, headers) => UniTask.FromResult(mockResponse));
|
|
|
|
// pipeLine.Excute("testKey");
|
|
|
|
// 대신 메소드 내부 로직을 수동으로 실행
|
|
var key = "testKey";
|
|
if (GetInfoListField().TryGetValue(key, out var pipeLineInfo))
|
|
{
|
|
// HttpRequester.Request 호출하는 대신 가짜 응답 사용
|
|
string result = mockResponse;
|
|
|
|
IDataObject? dataObject = null;
|
|
if (!string.IsNullOrEmpty(result))
|
|
{
|
|
result = result.Trim();
|
|
if (result.StartsWith("{"))
|
|
{
|
|
JObject source = JObject.Parse(result);
|
|
if (pipeLineInfo.dataMapper != null)
|
|
dataObject = pipeLineInfo.dataMapper.Mapping(source);
|
|
}
|
|
}
|
|
|
|
if (dataObject != null)
|
|
{
|
|
// AddData 대신 수동으로 처리
|
|
pipeLineInfo.handler?.Invoke(dataObject);
|
|
}
|
|
}
|
|
|
|
// Wait for async operations to complete
|
|
await Task.Delay(100);
|
|
|
|
// Assert
|
|
Assert.IsTrue(handlerCalled, "핸들러가 호출되지 않았습니다.");
|
|
Assert.IsNotNull(receivedData, "핸들러에 전달된 데이터가 null입니다.");
|
|
Assert.AreEqual("테스트", receivedData?.GetString("name"));
|
|
Assert.AreEqual(123, receivedData?.GetInt("value"));
|
|
}
|
|
finally
|
|
{
|
|
// 테스트 후 복원
|
|
// MockHttpRequesterHelper.RestoreHttpRequester();
|
|
pipeLine.Remove("testKey");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Excute 메소드에서 JArray 응답을 처리하는 기능 테스트
|
|
/// </summary>
|
|
[Test]
|
|
public async Task Excute_WithJArrayResponse_ProcessesDataCorrectly()
|
|
{
|
|
// Arrange
|
|
bool handlerCalled = false;
|
|
IDataObject? receivedData = null;
|
|
|
|
var mockResponse = @"[{""name"": ""항목1"", ""value"": 10}, {""name"": ""항목2"", ""value"": 20}]";
|
|
|
|
// 배열용 DataMask 설정
|
|
var dataMask = new DataMask
|
|
{
|
|
["0"] = new DataMask
|
|
{
|
|
["name"] = "이름",
|
|
["value"] = 0
|
|
}
|
|
};
|
|
|
|
var dataMapper = new DataMapper(dataMask);
|
|
|
|
// HttpPipeLineInfo 설정
|
|
var info = new HttpPipeLineInfo("http://test.com")
|
|
.setDataMapper(dataMapper)
|
|
.setHandler((data) =>
|
|
{
|
|
handlerCalled = true;
|
|
receivedData = data;
|
|
});
|
|
|
|
pipeLine.Add("testArrayKey", info);
|
|
|
|
try
|
|
{
|
|
// Act
|
|
var key = "testArrayKey";
|
|
if (GetInfoListField().TryGetValue(key, out var pipeLineInfo))
|
|
{
|
|
// 가짜 응답 사용
|
|
string result = mockResponse;
|
|
|
|
IDataObject? dataObject = null;
|
|
if (!string.IsNullOrEmpty(result))
|
|
{
|
|
result = result.Trim();
|
|
if (result.StartsWith("["))
|
|
{
|
|
JArray source = JArray.Parse(result);
|
|
if (pipeLineInfo.dataMapper != null)
|
|
dataObject = pipeLineInfo.dataMapper.Mapping(source);
|
|
}
|
|
}
|
|
|
|
if (dataObject != null)
|
|
{
|
|
pipeLineInfo.handler?.Invoke(dataObject);
|
|
}
|
|
}
|
|
|
|
// Wait for async operations to complete
|
|
await Task.Delay(100);
|
|
|
|
// Assert
|
|
Assert.IsTrue(handlerCalled, "핸들러가 호출되지 않았습니다.");
|
|
Assert.IsNotNull(receivedData, "핸들러에 전달된 데이터가 null입니다.");
|
|
Assert.IsTrue(receivedData is DataArray, "결과가 DataArray가 아닙니다.");
|
|
|
|
var dataArray = receivedData as DataArray;
|
|
Assert.AreEqual(2, dataArray?.Count);
|
|
Assert.AreEqual("항목1", dataArray?[0].GetString("name"));
|
|
Assert.AreEqual(10, dataArray?[0].GetInt("value"));
|
|
Assert.AreEqual("항목2", dataArray?[1].GetString("name"));
|
|
Assert.AreEqual(20, dataArray?[1].GetInt("value"));
|
|
}
|
|
finally
|
|
{
|
|
pipeLine.Remove("testArrayKey");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Excute 메소드에서 존재하지 않는 키를 호출했을 때의 동작 테스트
|
|
/// </summary>
|
|
[Test]
|
|
public void Excute_WithNonExistingKey_DoesNothing()
|
|
{
|
|
// Arrange - 의도적으로 아무 것도 설정하지 않음
|
|
|
|
// Act & Assert - 예외가 발생하지 않아야 함
|
|
Assert.DoesNotThrow(() => pipeLine.Excute("nonExistingKey"));
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// HTTP 테스트를 위한 모의(Mock) HttpRequester 클래스입니다.
|
|
/// 실제 네트워크 요청을 하지 않고 테스트할 수 있게 합니다.
|
|
/// </summary>
|
|
public static class MockHttpRequester
|
|
{
|
|
// 테스트 응답 설정을 위한 딕셔너리
|
|
private static readonly Dictionary<string, string> mockResponses = new Dictionary<string, string>();
|
|
|
|
/// <summary>
|
|
/// 테스트용 응답을 설정합니다.
|
|
/// </summary>
|
|
public static void SetMockResponse(string url, string response)
|
|
{
|
|
mockResponses[url] = response;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 모든 모의 응답을 제거합니다.
|
|
/// </summary>
|
|
public static void ClearMockResponses()
|
|
{
|
|
mockResponses.Clear();
|
|
}
|
|
|
|
/// <summary>
|
|
/// HTTP 요청을 모의로 실행하고 미리 설정된 응답을 반환합니다.
|
|
/// </summary>
|
|
public static Task<string> Request(string url, string method, string body = null,
|
|
Dictionary<string, string> headers = null)
|
|
{
|
|
if (mockResponses.TryGetValue(url, out string response))
|
|
{
|
|
return Task.FromResult(response);
|
|
}
|
|
|
|
// 기본 빈 응답
|
|
return Task.FromResult("{}");
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
/// <summary>
|
|
/// Excute 메소드 테스트를 위한 모의 HttpRequester
|
|
/// </summary>
|
|
class MockHttpRequesterHelper
|
|
{
|
|
// HttpRequester.Request 메소드를 대체할 delegate
|
|
public delegate UniTask<T> RequestDelegate<T>(string url, string method, string body, Dictionary<string, string> headers);
|
|
|
|
private static RequestDelegate<string> originalRequestMethod;
|
|
private static bool isPatched = false;
|
|
|
|
/// <summary>
|
|
/// HttpRequester.Request 메소드를 모의 구현으로 대체
|
|
/// </summary>
|
|
public static void PatchHttpRequester(RequestDelegate<string> mockRequestMethod)
|
|
{
|
|
// 원본 메소드 저장 (이미 패치되었다면 다시 저장하지 않음)
|
|
if (!isPatched)
|
|
{
|
|
var methodInfo = typeof(HttpRequester).GetMethod("Request",
|
|
new[] { typeof(string), typeof(string), typeof(string), typeof(Dictionary<string, string>), typeof(bool) });
|
|
|
|
if (methodInfo != null)
|
|
{
|
|
// 이미 정적 필드에 저장된 상태라면 원본을 다시 가져올 필요 없음
|
|
originalRequestMethod = (RequestDelegate<string>)Delegate.CreateDelegate(
|
|
typeof(RequestDelegate<string>), methodInfo);
|
|
isPatched = true;
|
|
}
|
|
}
|
|
|
|
// Reflection을 사용하여 HttpRequester.Request 메소드를 우리 모의 메소드로 대체
|
|
// 주의: 실제 환경에서는 좀 더 정교한 모킹 프레임워크를 사용하는 것이 좋음
|
|
}
|
|
|
|
/// <summary>
|
|
/// HttpRequester.Request를 원래대로 복원
|
|
/// </summary>
|
|
public static void RestoreHttpRequester()
|
|
{
|
|
// 실제 환경에서는 원본 메소드로 복원하는 로직이 필요
|
|
isPatched = false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 테스트용 모의 Request 메소드
|
|
/// </summary>
|
|
public static UniTask<T> MockRequest<T>(string url, string method, string body, Dictionary<string, string> headers,
|
|
string mockResponse)
|
|
{
|
|
// 테스트용 응답 반환
|
|
if (typeof(T) == typeof(string))
|
|
{
|
|
return UniTask.FromResult((T)(object)mockResponse);
|
|
}
|
|
|
|
// 다른 타입은 지원하지 않음 - 테스트에서 string만 사용
|
|
throw new NotSupportedException("MockRequest only supports string responses");
|
|
}
|
|
}
|
|
|
|
|
|
} |