Files
EnglewoodLAB/Assets/Sample/Data/MqttDataReceiverTests.cs

742 lines
33 KiB
C#

#nullable enable
using Cysharp.Threading.Tasks;
using System;
using System.Collections.Generic;
using UnityEngine;
using UVC.Data.Core;
using UVC.Data.Mqtt;
using UVC.Log;
namespace UVC.Sample.Data
{
/// <summary>
/// 샘플 코드입니다. DataSampleRunner Inspector에서 실행하거나 Play 버튼으로 확인합니다.
/// MqttDataReceiver 클래스의 테스트를 위한 테스트 클래스입니다.
/// </summary>
public class MqttDataReceiverTests
{
private MqttDataReceiver mqttReceiver;
private Dictionary<string, TestDataHandler> handlers;
private Dictionary<string, DataMask> dataMasks;
private readonly string[] topicNames = { "AGV", "CARRIER", "STOCKER_STACK", "ALL" };
public void Setup()
{
// 기본 테스트 환경 설정
mqttReceiver = new MqttDataReceiver();
mqttReceiver.SetDomainPort("localhost", 1883);
mqttReceiver.UseMockup = true; // 테스트에서는 항상 MockMQTTService 사용
// 핸들러와 데이터 마스크 초기화
handlers = new Dictionary<string, TestDataHandler>();
dataMasks = new Dictionary<string, DataMask>();
// 각 토픽별 핸들러와 데이터 마스크 설정
foreach (var topic in topicNames)
{
handlers[topic] = new TestDataHandler();
dataMasks[topic] = CreateDataMaskForTopic(topic);
}
}
public async UniTask RunAll()
{
Setup();
Debug.Log("===== MqttDataReceiver 테스트 시작 =====");
// 하나씩 테스트 해야 함
//await RunTestAsync(nameof(ExecutePipeLine_AllTopics_RegistersAndHandlesMessages), ExecutePipeLine_AllTopics_RegistersAndHandlesMessages);
//await RunTestAsync(nameof(RemoveTopic_ShouldStopReceivingMessages), RemoveTopic_ShouldStopReceivingMessages);
//await RunTestAsync(nameof(UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData), UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData);
//await RunTestAsync(nameof(UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData), UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData);
//RunTest(nameof(OnTopicMessage_ValidJsonObject_CallsHandler), OnTopicMessage_ValidJsonObject_CallsHandler);
//RunTest(nameof(OnTopicMessage_JsonArray_CallsHandler), OnTopicMessage_JsonArray_CallsHandler);
//RunTest(nameof(OnTopicMessage_EmptyMessage_DoesNotCallHandler), OnTopicMessage_EmptyMessage_DoesNotCallHandler);
//RunTest(nameof(OnTopicMessage_InvalidJson_DoesNotCallHandler), OnTopicMessage_InvalidJson_DoesNotCallHandler);
//await RunTestAsync(nameof(OnTopicMessage_WithValidData_ValidatorPassesAsync), OnTopicMessage_WithValidData_ValidatorPassesAsync);
//await RunTestAsync(nameof(OnTopicMessage_WithInvalidData_ValidatorFailsAsync), OnTopicMessage_WithInvalidData_ValidatorFailsAsync);
await RunTestAsync(nameof(OnTopicMessage_WithArrayAndValidator_FiltersInvalidDataAsync), OnTopicMessage_WithArrayAndValidator_FiltersInvalidDataAsync);
Debug.Log("===== MqttDataReceiver 테스트 완료 =====");
}
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}");
}
}
private async UniTask RunTestAsync(string testName, Func<UniTask> testAction)
{
try
{
Debug.Log($"테스트 시작: {testName}");
await testAction();
Debug.Log($"테스트 성공: {testName}");
}
catch (Exception ex)
{
Debug.LogError($"테스트 실패: {testName}\n{ex.Message}\n{ex.StackTrace}");
}
}
/// <summary>
/// 검증 헬퍼 메서드입니다.
/// </summary>
private static void Check(bool condition, string message)
{
if (!condition) throw new Exception($"검증 실패: {message}");
}
public void TearDown()
{
// 테스트 완료 후 리소스 정리
mqttReceiver.Stop();
mqttReceiver.Dispose();
}
private DataMask CreateDataMaskForTopic(string topic)
{
var dataMask = new DataMask();
dataMask.ObjectName = topic;
// 각 토픽별로 ObjectIdKey 설정
switch (topic)
{
case "AGV":
dataMask.ObjectIdKey = "VHL_NAME";
dataMask.ObjectName = "AGV";
dataMask["VHL_NAME"] = "HFF09CNA8013";
dataMask["AGV_IDX"] = 12;
dataMask["B_INSTALL"] = "Y";
dataMask["NODE_ID"] = 235;
dataMask["REAL_ID"] = 235;
dataMask["VHL_STATE"] = 11;
dataMask["BAY_LIST"] = "21;";
dataMask["X"] = 118301;
dataMask["Y"] = 20177;
dataMask["MODE"] = 1;
dataMask["BATT"] = 75;
dataMask["SUB_GOAL"] = 211;
dataMask["FINAL_GOAL"] = 1006;
dataMask["TIMESTAMP"] = "2025-03-25T12:00:00.980Z";
dataMask["DEGREE"] = 181.2;
dataMask["STOP_STATE"] = 0;
dataMask["JOB_ID"] = "2F24217_289_7038296224059039";
dataMask["DESTINATION_PORT"] = "HFF09MPI0200_LIP01";
dataMask["SOURCE_PORT"] = "HFF09AGM0100_UOP01";
dataMask["FROM"] = "HFF09AGM0100,NULL,0201012";
dataMask["TO"] = "HFF09MPI0200,HFF09MPI0200_LIP01,NULL";
dataMask["TRANSPORT_JOB_TIMESTAMP"] = "2025-03-25T05:40:19.000Z";
dataMask["FACTOR"] = 69.3;
dataMask["AGV_FACTOR_TIMESTAMP"] = DateTime.Now;
break;
case "CARRIER":
dataMask.ObjectIdKey = "MAIN_CARR_ID";
dataMask.ObjectName = "Carrier";
dataMask["MAIN_CARR_ID"] = "2F02365";
dataMask["SUB_CARR_ID"] = "2F02365,2F70671,2F28723";
dataMask["CARR_SEQ"] = "3";
dataMask["CARR_USE"] = "EMPTY";
dataMask["CURRENTPORT"] = "HFF09CNV0300_ABP3003";
dataMask["MOVESTATUS"] = "ARRIVED";
dataMask["MOVEFLAG"] = "0";
dataMask["PROD_DETAIL_CODE"] = "E3A";
dataMask["ASSIGN_LOT_QTY"] = "0";
dataMask["CARR_SIZE_TYPE"] = "STACK3";
dataMask["ABNM_VALUE"] = "0";
dataMask["LINE_ID"] = "FM0I";
dataMask["TIMESTAMP"] = DateTime.Now;
dataMask["GOOD_QTY"] = "0";
dataMask["CURRENTLOCATION"] = "HFF09CNV0300";
break;
case "STOCKER_STACK":
dataMask.ObjectIdKey = "STOCKER_NAME";
dataMask.ObjectName = "StokerStack";
dataMask["STOCKER_NAME"] = "HFF09AGN0300";
dataMask["CAPACITY"] = "89.57";
dataMask["MAXIMUM_CAPACITY"] = "834";
dataMask["TRAY_CAPACITY"] = "83.25";
dataMask["MAXIMUM_TRAY_CAPACITY"] = "2502";
dataMask["RACK_LOAD_COUNT"] = "747";
dataMask["RACK_EMPTY_COUNT"] = "87";
dataMask["RESERVATED_RETURN_COUNT"] = "5";
dataMask["TRAY_COUNT"] = "2083";
dataMask["TRAY_REWORK_COUNT_AVG"] = "3";
dataMask["TRAY_REWORK_COUNT_MAX"] = "153";
dataMask["TRAY_REWORK_COUNT_MIN"] = "0";
dataMask["RACK_DISABLE_COUNT"] = "4";
dataMask["KOR_EQP_NAME"] = "상온Aging #03";
dataMask["ENG_EQP_NAME"] = "상온Aging #03";
dataMask["TIMESTAMP"] = DateTime.Now;
dataMask["STEP"] = new List<DataMask>()
{
new DataMask {
ObjectIdKey = "STOCKER_NAME",
ObjectName = "StokerStep",
["STOCKER_NAME"] = "HFF09AGN0300",
["STEP_ID"] = "8106",
["RACK_STEP_COUNT"] = "88",
["TOTAL"] = "834",
["STEP_CAPACITY"] = "10.55",
},
};
break;
case "ALL":
// ALL 토픽은 ObjectIdKey 없음
dataMask["AGV"] = new List<DataMask>()
{
new DataMask
{
["VHL_NAME"] = "HFF09CNA8053",
["AGV_IDX"] = 52,
["X"] = 223316,
["Y"] = 218171,
["B_INSTALL"] = "Y",
["NODE_ID"] = 235,
["REAL_ID"] = 235,
["VHL_STATE"] = 11,
["BAY_LIST"] = "21;",
["MODE"] = 1,
["BATT"] = 75,
["SUB_GOAL"] = 211,
["FINAL_GOAL"] = 1006,
["TIMESTAMP"] = "2025-03-25T12:00:00.980Z",
["DEGREE"] = 181.2,
["STOP_STATE"] = 0,
["JOB_ID"] = "2F24217_289_7038296224059039",
["DESTINATION_PORT"] = "HFF09MPI0200_LIP01",
["SOURCE_PORT"] = "HFF09AGM0100_UOP01",
["FROM"] = "HFF09AGM0100,NULL,0201012",
["TO"] = "HFF09MPI0200,HFF09MPI0200_LIP01,NULL",
["TRANSPORT_JOB_TIMESTAMP"] = "2025-03-25T05:40:19.000Z",
["FACTOR"] = 69.3,
}
};
dataMask["CARRIER"] = new List<DataMask>()
{
new DataMask
{
["MAIN_CARR_ID"] = "2F02365",
["SUB_CARR_ID"] = "2F02365,2F70671,2F28723",
["CARR_SEQ"] = "3",
["CARR_USE"] = "EMPTY",
["CURRENTPORT"] = "HFF09CNV0300_ABP3003",
["MOVESTATUS"] = "ARRIVED",
["MOVEFLAG"] = "0",
["PROD_DETAIL_CODE"] = "E3A",
["ASSIGN_LOT_QTY"] = "0",
["CARR_SIZE_TYPE"] = "STACK3",
["ABNM_VALUE"] = "0",
["LINE_ID"] = "FM0I",
["TIMESTAMP"] = DateTime.Now,
["GOOD_QTY"] = "0",
["CURRENTLOCATION"] = "HFF09CNV0300",
}
};
dataMask["CARRIER"] = new List<DataMask>()
{
new DataMask
{
["STOCKER_NAME"] = "HFF09AGN0300",
["CAPACITY"] = "89.57",
["MAXIMUM_CAPACITY"] = "834",
["TRAY_CAPACITY"] = "83.25",
["MAXIMUM_TRAY_CAPACITY"] = "2502",
["RACK_LOAD_COUNT"] = "747",
["RACK_EMPTY_COUNT"] = "87",
["RESERVATED_RETURN_COUNT"] = "5",
["TRAY_COUNT"] = "2083",
["TRAY_REWORK_COUNT_AVG"] = "3",
["TRAY_REWORK_COUNT_MAX"] = "153",
["TRAY_REWORK_COUNT_MIN"] = "0",
["RACK_DISABLE_COUNT"] = "4",
["KOR_EQP_NAME"] = "상온Aging #03",
["ENG_EQP_NAME"] = "상온Aging #03",
["TIMESTAMP"] = DateTime.Now,
["STEP"] = new List<DataMask>
{
new DataMask {
["STOCKER_NAME"] = "HFF09AGN0300",
["STEP_ID"] = "8106",
["RACK_STEP_COUNT"] = "88",
["TOTAL"] = "834",
["STEP_CAPACITY"] = "10.55",
},
}
}
};
break;
}
return dataMask;
}
public async UniTask ExecutePipeLine_AllTopics_RegistersAndHandlesMessages()
{
// Arrange - 파이프라인 설정
foreach (var topic in topicNames)
{
// 필요한 UpdatedDataOnly 설정
bool updatedDataOnly = topic != "ALL";
var pipelineInfo = new MqttSubscriptionConfig(topic, updatedDataOnly)
.SetDataMapper(new DataMapper(dataMasks[topic]))
.SetHandler(handlers[topic].HandleData);
mqttReceiver.Add(pipelineInfo);
}
Debug.Log("파이프라인 설정 완료.");
// Act - 파이프라인 실행
mqttReceiver.Start();
Debug.Log("파이프라인 Start.");
// Assert - 일정 시간 기다린 후 각 핸들러가 호출되었는지 확인
await UniTask.Delay(1500);
// 각 토픽별로 핸들러가 호출되었는지 확인
foreach (var topic in topicNames)
{
Check(handlers[topic].CallCount > 0, $"{topic} 토픽의 핸들러가 호출되지 않았습니다.");
if (topic != "ALL")
{
// ALL을 제외한 토픽은 ObjectIdKey가 설정되어 있어야 함
Check(handlers[topic].LastDataObject != null, $"{topic} 토픽의 핸들러에 전달된 DataObject가 null입니다.");
}
}
}
public async UniTask RemoveTopic_ShouldStopReceivingMessages()
{
// Arrange
// AGV 토픽만 등록
var agvInfo = new MqttSubscriptionConfig("AGV", true)
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
mqttReceiver.Add(agvInfo);
mqttReceiver.Start();
// 메시지가 수신되도록 잠시 대기
await UniTask.Delay(1000);
// 초기 호출 횟수 저장
int initialCallCount = handlers["AGV"].CallCount;
Check(initialCallCount > 0, "초기 AGV 토픽의 핸들러가 호출되지 않았습니다.");
// Act
mqttReceiver.Remove("AGV"); // AGV 토픽 제거
// 핸들러 초기화
handlers["AGV"].Reset();
// 충분한 시간 대기
await UniTask.Delay(1500);
// Assert
Check(handlers["AGV"].CallCount == 0, $"{handlers["AGV"].CallCount} != 0, 토픽을 제거했지만 핸들러가 여전히 호출되고 있습니다.");
}
public async UniTask UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData()
{
// Arrange - 파이프라인 설정
// TestMqttDataReceiver을 사용하여 직접 메시지를 보낼 수 있게 함
var testPipeLine = new TestMqttDataReceiver();
// UpdatedDataOnly가 true인 AGV 토픽 추가
var agvInfo = new MqttSubscriptionConfig("AGV", true)
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(agvInfo);
// Mock 데이터 생성 (업데이트가 있는 데이터)
string jsonWithUpdates = "{\"VHL_NAME\":\"HFF09CNA8053\",\"AGV_IDX\":52,\"X\":223316,\"Y\":218171}";
// Act
// 첫 번째 메시지 전송 (초기 데이터)
testPipeLine.TestOnTopicMessage("AGV", jsonWithUpdates);
// 동일한 메시지 다시 전송 (변경 없음)
testPipeLine.TestOnTopicMessage("AGV", jsonWithUpdates);
// Assert
// UpdatedDataOnly가 true이므로 두 번째 메시지는 핸들러를 호출하지 않아야 함
// 실제로는 DataRepository가 필요하므로 완전한 테스트는 어려움
// 여기서는 최소한 첫 번째 메시지가 처리되었는지 확인
Check(handlers["AGV"].CallCount > 0, "AGV 토픽의 핸들러가 호출되지 않았습니다.");
}
public void OnTopicMessage_ValidJsonObject_CallsHandler()
{
// Arrange
var testPipeLine = new TestMqttDataReceiver();
foreach (var topic in topicNames)
{
bool updatedDataOnly = topic != "ALL";
var pipelineInfo = new MqttSubscriptionConfig(topic, updatedDataOnly)
.SetDataMapper(new DataMapper(dataMasks[topic]))
.SetHandler(handlers[topic].HandleData);
testPipeLine.Add(pipelineInfo);
}
// Act
// 각 토픽에 대해 유효한 JSON 메시지 전송
testPipeLine.TestOnTopicMessage("AGV", "{\"VHL_NAME\":\"HFF09CNA8053\",\"AGV_IDX\":52}");
testPipeLine.TestOnTopicMessage("CARRIER", "{\"MAIN_CARR_ID\":\"2F02365\",\"SUB_CARR_ID\":\"2F02365,2F70671,2F28723\"}");
testPipeLine.TestOnTopicMessage("STOCKER_STACK", "{\"STOCKER_NAME\":\"HFF09AGN0300\",\"CAPACITY\":\"89.57\"}");
testPipeLine.TestOnTopicMessage("ALL", "{\"key\":\"value\"}");
// Assert
// 각 토픽의 핸들러가 호출되었는지 확인
foreach (var topic in topicNames)
{
Check(handlers[topic].CallCount == 1, $"{handlers[topic].CallCount} != 1, {topic} 토픽의 핸들러가 정확히 한 번 호출되어야 합니다.");
}
}
public void OnTopicMessage_JsonArray_CallsHandler()
{
// Arrange
var testPipeLine = new TestMqttDataReceiver();
var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo);
// JSON 배열 메시지 생성
string jsonArrayMessage =
"[{\"VHL_NAME\":\"HFF09CNA8053\",\"AGV_IDX\":52},{\"VHL_NAME\":\"HFF09CNA8033\",\"AGV_IDX\":32}]";
// Act
testPipeLine.TestOnTopicMessage("AGV", jsonArrayMessage);
// Assert
Check(handlers["AGV"].CallCount == 1, $"{handlers["AGV"].CallCount} != 1, AGV 토픽의 핸들러가 정확히 한 번 호출되어야 합니다.");
Check(handlers["AGV"].LastDataObject != null, "핸들러에 전달된 DataObject가 null입니다.");
}
public void OnTopicMessage_EmptyMessage_DoesNotCallHandler()
{
// Arrange
var testPipeLine = new TestMqttDataReceiver();
var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo);
// Act
testPipeLine.TestOnTopicMessage("AGV", ""); // 빈 메시지 전송
// Assert
Check(handlers["AGV"].CallCount == 0, $"{handlers["AGV"].CallCount} != 0, 빈 메시지가 전달되었을 때 핸들러가 호출되지 않아야 합니다.");
}
public void OnTopicMessage_InvalidJson_DoesNotCallHandler()
{
// Arrange
var testPipeLine = new TestMqttDataReceiver();
var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.SetDataMapper(new DataMapper(dataMasks["AGV"]))
.SetHandler(handlers["AGV"].HandleData);
testPipeLine.Add(pipelineInfo);
// Act - 잘못된 JSON 형식의 메시지 전송
testPipeLine.TestOnTopicMessage("AGV", "{invalid json}");
// Assert
Check(handlers["AGV"].CallCount == 0, $"{handlers["AGV"].CallCount} != 0, 잘못된 JSON 형식의 메시지가 전달되었을 때 핸들러가 호출되지 않아야 합니다.");
}
public async UniTask UpdatedDataOnly_WithMockMQTTService_ShouldOnlyReceiveUpdatedData()
{
// Arrange
// 1. 데이터 핸들러를 통해 수신된 데이터 추적
var handler = new UpdatedDataTrackingHandler();
// 2. AGV 토픽을 위한 데이터 마스크 설정
var dataMask = new DataMask();
dataMask.ObjectName = "AGV";
dataMask.ObjectIdKey = "VHL_NAME";
// 3. MqttDataReceiver 설정 (MockMQTTService 사용)
var pipeline = new MqttDataReceiver();
pipeline.SetDomainPort("localhost", 1883);
pipeline.UseMockup = true; // MockMQTTService 사용 설정
// 4. UpdatedDataOnly=true로 토픽 등록
var pipelineInfo = new MqttSubscriptionConfig("AGV", true)
.SetDataMapper(new DataMapper(dataMask))
.SetHandler(handler.HandleData);
pipeline.Add(pipelineInfo);
// Act
// 파이프라인 실행 - 이것이 MockMQTTService를 통해 메시지를 보내기 시작
pipeline.Start();
// 첫 번째 데이터 세트가 수신될 때까지 대기
await UniTask.Delay(1500);
// 첫 번째 데이터 세트의 콜백 수를 기록
int initialCallCount = handler.CallCount;
Check(initialCallCount > 0, "첫 번째 메시지 세트가 수신되지 않았습니다.");
// AGV 항목 수 저장
int initialAgvCount = handler.ReceivedAgvItems.Count;
Check(initialAgvCount > 0, "첫 번째 메시지에 AGV 항목이 없습니다.");
// 첫 번째 콜백에서 수신된 업데이트 항목 수 기록
int firstUpdatedCount = handler.LastUpdatedCount;
Check(firstUpdatedCount > 0, "첫 번째 메시지에 업데이트된 데이터가 없습니다.");
// 다음 데이터 세트가 도착하기를 기다림
await UniTask.Delay(3000);
// 두 번째 메시지가 도착했는지 확인
int finalCallCount = handler.CallCount;
Check(finalCallCount > initialCallCount, "두 번째 메시지 세트가 수신되지 않았습니다.");
// Assert
// 기본 검사: 모든 호출에서 업데이트된 데이터만 전송되었는지 확인
foreach (int updatedCount in handler.UpdatedCounts)
{
Check(updatedCount > 0, "업데이트된 데이터가 없는 콜백이 있습니다.");
}
// 두 번째 메시지에서 처음 도착한 AGV 항목과 추가/변경된 항목이 있는지 확인
// MockMQTTService는 매번 다른 데이터 세트를 보내므로 이런 차이가 있어야 함
bool hasChanges = handler.ReceivedAgvItems.Count > initialAgvCount ||
handler.HasUpdatedExistingItems;
Check(hasChanges, "두 번째 메시지에서 변경된 데이터가 감지되지 않았습니다.");
// 정리
pipeline.Stop();
pipeline.Dispose();
}
// UpdatedDataOnly 테스트를 위한 특수 핸들러
public class UpdatedDataTrackingHandler
{
public int CallCount { get; private set; } = 0;
public List<int> UpdatedCounts { get; private set; } = new List<int>();
public int LastUpdatedCount { get; private set; } = 0;
public HashSet<string> ReceivedAgvItems { get; private set; } = new HashSet<string>();
public bool HasUpdatedExistingItems { get; private set; } = false;
public void HandleData(IDataObject? dataObject)
{
CallCount++;
ULog.Debug($"UpdatedDataTrackingHandler 호출됨. CallCount: {CallCount}");
if (dataObject != null)
{
// 업데이트 개수 기록
LastUpdatedCount = dataObject.UpdatedCount;
UpdatedCounts.Add(dataObject.UpdatedCount);
// AGV 데이터 분석
if (dataObject is DataArray agvData)
{
foreach (var item in agvData)
{
if (item is DataObject obj && obj.Name == "AGV" && obj["VHL_NAME"] != null)
{
string vhlName = obj["VHL_NAME"].ToString();
// 이미 존재하는 항목이 업데이트된 경우
if (ReceivedAgvItems.Contains(vhlName))
{
HasUpdatedExistingItems = true;
}
// 새로운 항목 추가
ReceivedAgvItems.Add(vhlName);
}
}
}
}
}
}
#region DataValidator Tests
/// <summary>
/// DataValidator를 사용하여 유효한 데이터를 성공적으로 처리하는지 테스트합니다.
/// </summary>
public async UniTask OnTopicMessage_WithValidData_ValidatorPassesAsync()
{
// Arrange
var testPipeLine = new TestMqttDataReceiver();
var handler = new TestDataHandler();
var dataMapper = new DataMapper(new DataMask { ["Id"] = 0, ["status"] = "" });
// "status" 필드가 "active"인 경우에만 유효하도록 설정
var validator = new DataValidator();
validator.AddValidator("status", value =>
{
Debug.Log($"Validator called with value: {value}, {value is string s2 && s2 == "active"}");
return value is string s && s == "active";
});
var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.SetDataMapper(dataMapper)
.SetValidator(validator)
.SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo);
var validMessage = @"{""Id"": 1, ""status"": ""active""}";
// Act
testPipeLine.TestOnTopicMessage("test_topic", validMessage);
await UniTask.Delay(2000); // 핸들러가 호출되기 전에 잠시 대기
// Assert
Check(handler.CallCount == 1, $"{handler.CallCount} != 1, 유효성 검사를 통과했으므로 핸들러가 호출되어야 합니다.");
Check(handler.LastDataObject != null, "데이터가 핸들러로 전달되어야 합니다.");
Check((handler.LastDataObject as DataObject)?.GetString("status") == "active", $"{(handler.LastDataObject as DataObject)?.GetString("status")} != active");
}
/// <summary>
/// DataValidator를 사용하여 유효하지 않은 데이터를 필터링하는지 테스트합니다.
/// </summary>
public async UniTask OnTopicMessage_WithInvalidData_ValidatorFailsAsync()
{
// Arrange
var testPipeLine = new TestMqttDataReceiver();
var handler = new TestDataHandler();
var dataMapper = new DataMapper(new DataMask { ["Id"] = 0, ["status"] = "" });
// "status" 필드가 "active"인 경우에만 유효하도록 설정
var validator = new DataValidator();
validator.AddValidator("status", value =>
{
Debug.Log($"Validator called with value2: {value}, {value is string s2 && s2 == "active"}");
return value is string s && s == "active";
});
var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.SetDataMapper(dataMapper)
.SetValidator(validator)
.SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo);
var invalidMessage = @"{""Id"": 2, ""status"": ""inactive""}";
// Act
testPipeLine.TestOnTopicMessage("test_topic", invalidMessage);
await UniTask.Delay(2000); // 핸들러가 호출되기 전에 잠시 대기
// Assert
Check(handler.CallCount == 0, $"{handler.CallCount} != 0, 유효성 검사에 실패했으므로 핸들러가 호출되지 않아야 합니다.");
}
/// <summary>
/// DataValidator가 배열 데이터에서 유효한 항목만 필터링하는지 테스트합니다.
/// </summary>
public async UniTask OnTopicMessage_WithArrayAndValidator_FiltersInvalidDataAsync()
{
// Arrange
var testPipeLine = new TestMqttDataReceiver();
var handler = new TestDataHandler();
var dataMapper = new DataMapper(new DataMask { ["Id"] = 0, ["value"] = 0 });
// "value"가 15보다 큰 항목만 유효하도록 설정
var validator = new DataValidator();
validator.AddValidator("value", value =>
{
Debug.Log($"Validator called with value3: {value}, {value is int v2 && v2 > 15}");
return value is int v && v > 15;
});
var pipelineInfo = new MqttSubscriptionConfig("test_topic")
.SetDataMapper(dataMapper)
.SetValidator(validator)
.SetHandler(handler.HandleData);
testPipeLine.Add(pipelineInfo);
var arrayMessage = @"[
{""Id"": 1, ""value"": 10},
{""Id"": 2, ""value"": 20},
{""Id"": 3, ""value"": 5}
]";
// Act
testPipeLine.TestOnTopicMessage("test_topic", arrayMessage);
await UniTask.Delay(2000); // 핸들러가 호출되기 전에 잠시 대기
Debug.Log($"LastDataObject: {handler.LastDataObject.GetType()}");
// Assert
Check(handler.CallCount == 1, $"{handler.CallCount} != 1, 핸들러가 한 번 호출되어야 합니다.");
Check(handler.LastDataObject != null, "데이터가 핸들러로 전달되어야 합니다.");
Check(handler.LastDataObject is DataArray, "결과는 DataArray여야 합니다.");
var dataArray = handler.LastDataObject as DataArray;
Check(dataArray.Count == 1, $"{dataArray.Count} != 1, 유효한 항목은 1개여야 합니다.");
Check(dataArray[0].GetInt("value") == 20, $"{dataArray[0].GetInt("value")} != 20, 필터링된 데이터의 값이 올바르지 않습니다.");
}
#endregion
}
// MqttDataReceiver의 OnTopicMessage 메서드를 테스트하기 위한 확장 클래스
public class TestMqttDataReceiver : MqttDataReceiver
{
public TestMqttDataReceiver() : base()
{
UseMockup = true;
SetDomainPort("localhost", 1883);
}
public void TestOnTopicMessage(string topic, string message)
{
// private 메서드에 접근하기 위한 래퍼
typeof(MqttDataReceiver).GetMethod("OnTopicMessage",
System.Reflection.BindingFlags.NonPublic |
System.Reflection.BindingFlags.Instance)?.Invoke(this, new object[] { topic, message });
}
}
// 테스트용 데이터 핸들러 클래스
public class TestDataHandler
{
public int CallCount { get; private set; } = 0;
public IDataObject? LastDataObject { get; private set; } = null;
public void HandleData(IDataObject? dataObject)
{
CallCount++;
LastDataObject = dataObject;
}
public void Reset()
{
CallCount = 0;
LastDataObject = null;
}
}
}