742 lines
33 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|