MQTTPipeLineTests test 중
This commit is contained in:
@@ -223,7 +223,7 @@ namespace SQLite4Unity3d
|
||||
/// var db = new SQLiteConnection(Application.persistentDataPath + "/myDatabase.db", true);
|
||||
///
|
||||
/// // 테이블 생성
|
||||
/// db.CreateTable<Person>();
|
||||
/// db.CreateTable<Person>();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = false)
|
||||
@@ -438,7 +438,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 제네릭 타입으로 매핑 얻기
|
||||
/// var mapping = db.GetMapping<Person>();
|
||||
/// var mapping = db.GetMapping<Person>();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public TableMapping GetMapping<T>()
|
||||
@@ -466,7 +466,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Person 테이블 삭제
|
||||
/// db.DropTable<Person>();
|
||||
/// db.DropTable<Person>();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public int DropTable<T>()
|
||||
@@ -490,10 +490,10 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // Person 클래스를 표현하는 테이블 생성
|
||||
/// db.CreateTable<Person>();
|
||||
/// db.CreateTable<Person>();
|
||||
///
|
||||
/// // 암시적 인덱스 생성 옵션 사용
|
||||
/// db.CreateTable<Person>(CreateFlags.ImplicitIndex);
|
||||
/// db.CreateTable<Person>(CreateFlags.ImplicitIndex);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public int CreateTable<T>(CreateFlags createFlags = CreateFlags.None)
|
||||
@@ -675,7 +675,7 @@ namespace SQLite4Unity3d
|
||||
|
||||
/// <summary>
|
||||
/// 지정된 객체 속성에 대한 인덱스를 생성합니다.
|
||||
/// 예시: CreateIndex<Client>(c => c.Name);
|
||||
/// 예시: CreateIndex<Client>(c => c.Name);
|
||||
/// </summary>
|
||||
/// <typeparam name="T">데이터베이스 테이블에 반영할 타입.</typeparam>
|
||||
/// <param name="property">인덱싱할 속성</param>
|
||||
@@ -683,7 +683,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 람다 식을 사용하여 속성에 대한 인덱스 생성
|
||||
/// db.CreateIndex<Person>(p => p.Email, true);
|
||||
/// db.CreateIndex<Person>(p => p.Email, true);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public void CreateIndex<T>(Expression<Func<T, object>> property, bool unique = false)
|
||||
@@ -821,7 +821,7 @@ namespace SQLite4Unity3d
|
||||
/// <code>
|
||||
/// // 파라미터가 있는 명령 생성
|
||||
/// var cmd = db.CreateCommand("SELECT * FROM Person WHERE Id = ?", 1);
|
||||
/// var person = cmd.ExecuteQuery<Person>().FirstOrDefault();
|
||||
/// var person = cmd.ExecuteQuery<Person>().FirstOrDefault();
|
||||
/// </code>
|
||||
/// </example>
|
||||
public SQLiteCommand CreateCommand(string cmdText, params object[] ps)
|
||||
@@ -897,8 +897,8 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 스칼라 값 가져오기
|
||||
/// int count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||
/// string name = db.ExecuteScalar<string>("SELECT Name FROM Person WHERE Id = ?", 1);
|
||||
/// int count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||
/// string name = db.ExecuteScalar<string>("SELECT Name FROM Person WHERE Id = ?", 1);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public T ExecuteScalar<T>(string query, params object[] args)
|
||||
@@ -944,7 +944,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 조건에 맞는 모든 Person 객체 조회
|
||||
/// var adults = db.Query<Person>("SELECT * FROM Person WHERE Age >= ?", 18);
|
||||
/// var adults = db.Query<Person>("SELECT * FROM Person WHERE Age >= ?", 18);
|
||||
/// foreach (var person in adults) {
|
||||
/// Console.WriteLine($"성인: {person.Name}, {person.Age}세");
|
||||
/// }
|
||||
@@ -975,7 +975,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 대용량 데이터를 지연 로딩으로 처리
|
||||
/// var query = db.DeferredQuery<Person>("SELECT * FROM Person");
|
||||
/// var query = db.DeferredQuery<Person>("SELECT * FROM Person");
|
||||
/// using (var enumerator = query.GetEnumerator()) {
|
||||
/// while (enumerator.MoveNext()) {
|
||||
/// var person = enumerator.Current;
|
||||
@@ -1061,7 +1061,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // LINQ를 사용하여 데이터 쿼리하기
|
||||
/// var query = db.Table<Person>()
|
||||
/// var query = db.Table<Person>()
|
||||
/// .Where(p => p.Age > 18)
|
||||
/// .OrderBy(p => p.Name);
|
||||
///
|
||||
@@ -1091,7 +1091,7 @@ namespace SQLite4Unity3d
|
||||
/// <code>
|
||||
/// // 기본 키로 Person 객체 가져오기
|
||||
/// try {
|
||||
/// var person = db.Get<Person>(1);
|
||||
/// var person = db.Get<Person>(1);
|
||||
/// Console.WriteLine($"이름: {person.Name}");
|
||||
/// } catch (Exception) {
|
||||
/// Console.WriteLine("해당 ID의 Person을 찾을 수 없습니다");
|
||||
@@ -1118,7 +1118,7 @@ namespace SQLite4Unity3d
|
||||
/// <code>
|
||||
/// // 조건식으로 Person 객체 가져오기
|
||||
/// try {
|
||||
/// var person = db.Get<Person>(p => p.Email == "test@example.com");
|
||||
/// var person = db.Get<Person>(p => p.Email == "test@example.com");
|
||||
/// Console.WriteLine($"이름: {person.Name}");
|
||||
/// } catch (Exception) {
|
||||
/// Console.WriteLine("조건에 맞는 Person을 찾을 수 없습니다");
|
||||
@@ -1145,7 +1145,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 기본 키로 Person 객체 찾기 (없으면 null 반환)
|
||||
/// var person = db.Find<Person>(1);
|
||||
/// var person = db.Find<Person>(1);
|
||||
/// if (person != null) {
|
||||
/// Console.WriteLine($"찾았습니다: {person.Name}");
|
||||
/// } else {
|
||||
@@ -1203,7 +1203,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 조건식으로 Person 객체 찾기 (없으면 null 반환)
|
||||
/// var person = db.Find<Person>(p => p.Email == "test@example.com");
|
||||
/// var person = db.Find<Person>(p => p.Email == "test@example.com");
|
||||
/// if (person != null) {
|
||||
/// Console.WriteLine($"찾았습니다: {person.Name}");
|
||||
/// } else {
|
||||
@@ -1616,10 +1616,10 @@ namespace SQLite4Unity3d
|
||||
/// // RunInDatabaseLock을 사용한 스레드 안전한 데이터베이스 접근
|
||||
/// db.RunInDatabaseLock(() => {
|
||||
/// // 이 블록 내의 작업은 다른 스레드에서 동일한 데이터베이스에 접근할 수 없습니다.
|
||||
/// var count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||
/// var count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||
///
|
||||
/// if (count > 0) {
|
||||
/// var people = db.Query<Person>("SELECT * FROM Person");
|
||||
/// var people = db.Query<Person>("SELECT * FROM Person");
|
||||
/// foreach (var person in people) {
|
||||
/// // 안전하게 데이터 처리
|
||||
/// ProcessPerson(person);
|
||||
@@ -1648,7 +1648,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 객체 목록을 한 번에 삽입
|
||||
/// var people = new List<Person>
|
||||
/// var people = new List<Person>
|
||||
/// {
|
||||
/// new Person { Name = "홍길동", Age = 30 },
|
||||
/// new Person { Name = "김철수", Age = 25 },
|
||||
@@ -1687,7 +1687,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 여러 객체 한 번에 삽입 (충돌 무시 옵션 사용)
|
||||
/// var people = new List<Person>
|
||||
/// var people = new List<Person>
|
||||
/// {
|
||||
/// new Person { Id = 1, Name = "홍길동", Age = 30 },
|
||||
/// new Person { Id = 2, Name = "김철수", Age = 25 },
|
||||
@@ -1726,7 +1726,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 동적 타입으로 여러 객체 한 번에 삽입
|
||||
/// var people = new List<object>
|
||||
/// var people = new List<object>
|
||||
/// {
|
||||
/// new Person { Name = "홍길동", Age = 30 },
|
||||
/// new Person { Name = "김철수", Age = 25 }
|
||||
@@ -1798,7 +1798,7 @@ namespace SQLite4Unity3d
|
||||
/// db.InsertOrReplace(new Person { Id = 1, Name = "홍길동(수정)", Age = 31 });
|
||||
///
|
||||
/// // 값이 변경된 객체를 교체 삽입하는 방식으로 업데이트할 수도 있음
|
||||
/// var person = db.Get<Person>(1);
|
||||
/// var person = db.Get<Person>(1);
|
||||
/// person.Age += 1;
|
||||
/// db.InsertOrReplace(person);
|
||||
/// </code>
|
||||
@@ -2027,7 +2027,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 데이터베이스에서 객체 검색
|
||||
/// var person = db.Get<Person>(1);
|
||||
/// var person = db.Get<Person>(1);
|
||||
///
|
||||
/// // 객체 속성 변경
|
||||
/// person.Name = "홍길동(수정됨)";
|
||||
@@ -2127,7 +2127,7 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 여러 객체 한 번에 업데이트
|
||||
/// var people = db.Query<Person>("SELECT * FROM Person WHERE Age < 30");
|
||||
/// var people = db.Query<Person>("SELECT * FROM Person WHERE Age < 30");
|
||||
/// foreach (var person in people) {
|
||||
/// person.Age++;
|
||||
/// }
|
||||
@@ -2161,14 +2161,14 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 데이터베이스에서 객체 검색
|
||||
/// var person = db.Get<Person>(1);
|
||||
/// var person = db.Get<Person>(1);
|
||||
///
|
||||
/// // 객체 삭제
|
||||
/// int rowsAffected = db.Delete(person);
|
||||
/// Console.WriteLine($"삭제된 행 수: {rowsAffected}");
|
||||
///
|
||||
/// // 또는 직접 ID로 삭제
|
||||
/// rowsAffected = db.Delete<Person>(2);
|
||||
/// rowsAffected = db.Delete<Person>(2);
|
||||
/// </code>
|
||||
/// </example>
|
||||
public int Delete(object objectToDelete)
|
||||
@@ -2198,13 +2198,13 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 기본 키로 직접 객체 삭제
|
||||
/// int rowsDeleted = db.Delete<Person>(1);
|
||||
/// int rowsDeleted = db.Delete<Person>(1);
|
||||
/// Console.WriteLine($"ID가 1인 Person 객체 {rowsDeleted}개 삭제됨");
|
||||
///
|
||||
/// // 여러 ID를 차례로 삭제
|
||||
/// int[] idsToDelete = { 2, 3, 4 };
|
||||
/// foreach (int id in idsToDelete) {
|
||||
/// db.Delete<Person>(id);
|
||||
/// db.Delete<Person>(id);
|
||||
/// }
|
||||
/// </code>
|
||||
/// </example>
|
||||
@@ -2237,11 +2237,11 @@ namespace SQLite4Unity3d
|
||||
/// db.BeginTransaction();
|
||||
/// try {
|
||||
/// // 삭제 전 확인을 위한 카운트
|
||||
/// int beforeCount = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||
/// int beforeCount = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||
/// Console.WriteLine($"삭제 전 데이터 수: {beforeCount}");
|
||||
///
|
||||
/// // 모든 Person 데이터 삭제
|
||||
/// int deleted = db.DeleteAll<Person>();
|
||||
/// int deleted = db.DeleteAll<Person>();
|
||||
///
|
||||
/// Console.WriteLine($"삭제된 데이터 수: {deleted}");
|
||||
/// db.Commit();
|
||||
@@ -2272,7 +2272,7 @@ namespace SQLite4Unity3d
|
||||
/// using (var db = new SQLiteConnection("database.db"))
|
||||
/// {
|
||||
/// // 데이터베이스 작업 수행
|
||||
/// var people = db.Query<Person>("SELECT * FROM Person");
|
||||
/// var people = db.Query<Person>("SELECT * FROM Person");
|
||||
///
|
||||
/// // using 블록이 끝나면 자동으로 db.Dispose() 호출됨
|
||||
/// }
|
||||
@@ -2282,7 +2282,7 @@ namespace SQLite4Unity3d
|
||||
/// try
|
||||
/// {
|
||||
/// // 작업 수행
|
||||
/// connection.CreateTable<Person>();
|
||||
/// connection.CreateTable<Person>();
|
||||
/// }
|
||||
/// finally
|
||||
/// {
|
||||
@@ -2312,7 +2312,7 @@ namespace SQLite4Unity3d
|
||||
/// // 또는 using 문을 사용하여 자동으로 닫기
|
||||
/// using (var db = new SQLiteConnection("database.db")) {
|
||||
/// // 데이터베이스 작업 수행
|
||||
/// var count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||
/// var count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||
/// Console.WriteLine($"Person 테이블의 행 수: {count}");
|
||||
///
|
||||
/// // using 블록이 끝나면 자동으로 db.Dispose()가 호출되어 연결이 닫힘
|
||||
@@ -3454,11 +3454,11 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 조건식으로 데이터 필터링
|
||||
/// var adults = db.Table<Person>()
|
||||
/// var adults = db.Table<Person>()
|
||||
/// .Where(p => p.Age >= 18);
|
||||
///
|
||||
/// // 여러 조건 체이닝
|
||||
/// var adultMales = db.Table<Person>()
|
||||
/// var adultMales = db.Table<Person>()
|
||||
/// .Where(p => p.Age >= 18)
|
||||
/// .Where(p => p.Gender == "남성");
|
||||
///
|
||||
@@ -3519,10 +3519,10 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 나이순으로 정렬
|
||||
/// var peopleByAge = db.Table<Person>().OrderBy(p => p.Age);
|
||||
/// var peopleByAge = db.Table<Person>().OrderBy(p => p.Age);
|
||||
///
|
||||
/// // 이름순으로 정렬
|
||||
/// var peopleByName = db.Table<Person>().OrderBy(p => p.Name);
|
||||
/// var peopleByName = db.Table<Person>().OrderBy(p => p.Name);
|
||||
///
|
||||
/// foreach (var person in peopleByAge) {
|
||||
/// Console.WriteLine($"{person.Name}, {person.Age}세");
|
||||
@@ -3543,10 +3543,10 @@ namespace SQLite4Unity3d
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 나이 내림차순으로 정렬
|
||||
/// var olderFirst = db.Table<Person>().OrderByDescending(p => p.Age);
|
||||
/// var olderFirst = db.Table<Person>().OrderByDescending(p => p.Age);
|
||||
///
|
||||
/// // 이름 내림차순으로 정렬
|
||||
/// var reverseAlpha = db.Table<Person>().OrderByDescending(p => p.Name);
|
||||
/// var reverseAlpha = db.Table<Person>().OrderByDescending(p => p.Name);
|
||||
///
|
||||
/// foreach (var person in olderFirst) {
|
||||
/// Console.WriteLine($"{person.Name}, {person.Age}세");
|
||||
|
||||
@@ -77,7 +77,7 @@ namespace UVC.Data
|
||||
/// var mapper = new DataMapper(maskJson);
|
||||
/// DataObject result = mapper.Map(sourceJson);
|
||||
/// Debug.Log(result["name"].ToString()); // "김철수"
|
||||
/// Debug.Log(result["age"].ToObject<int>()); // 30
|
||||
/// Debug.Log(result["age"].ToObject<int>()); // 30
|
||||
/// </code>
|
||||
/// </example>
|
||||
public DataObject Mapping(JObject source)
|
||||
|
||||
@@ -30,7 +30,7 @@ namespace UVC.Data
|
||||
/// mask.ObjectName = "employees"; // 데이터 객체의 이름 지정
|
||||
///
|
||||
/// // 필드 이름 변환 규칙 설정
|
||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||
/// {
|
||||
/// { "full_name", "name" }, // JSON의 full_name을 name으로 변환
|
||||
/// { "employee_age", "age" } // JSON의 employee_age를 age로 변환
|
||||
@@ -87,7 +87,7 @@ namespace UVC.Data
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// var mask = new DataMask();
|
||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||
/// {
|
||||
/// { "first_name", "firstName" },
|
||||
/// { "last_name", "lastName" },
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace UVC.Data
|
||||
/// mask.ObjectName = "users";
|
||||
///
|
||||
/// // 필드 이름 변환 설정
|
||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||
/// {
|
||||
/// { "userName", "name" },
|
||||
/// { "userEmail", "email" }
|
||||
|
||||
@@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq;
|
||||
using SampleProject.Config;
|
||||
using System.Collections.Generic;
|
||||
using UVC.network;
|
||||
using UVC.Tests;
|
||||
|
||||
namespace UVC.Data
|
||||
{
|
||||
@@ -17,6 +18,16 @@ namespace UVC.Data
|
||||
/// </remarks>
|
||||
public class MQTTPipeLine
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// 테스트를 위한 목업 모드 활성화 여부를 설정하거나 가져옵니다.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// true로 설정하면 실제 MQTT 요청 대신 MQTTPipeLine를 사용합니다.
|
||||
/// 테스트 환경에서 외부 의존성 없이 MQTT 통신을 시뮬레이션할 때 유용합니다.
|
||||
/// </remarks>
|
||||
public bool UseMockup { get; internal set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// MQTT 브로커의 도메인 주소
|
||||
/// </summary>
|
||||
@@ -37,6 +48,8 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
private MQTTService mqtt;
|
||||
|
||||
private MockMQTTService? mockupMQTT;
|
||||
|
||||
/// <summary>
|
||||
/// MQTTPipeLine 인스턴스를 생성합니다.
|
||||
/// </summary>
|
||||
@@ -86,11 +99,24 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
public void Execute()
|
||||
{
|
||||
foreach (var topic in infoList.Keys)
|
||||
if (!UseMockup)
|
||||
{
|
||||
mqtt.AddTopicHandler(topic, OnTopicMessage);
|
||||
foreach (var topic in infoList.Keys)
|
||||
{
|
||||
mqtt.AddTopicHandler(topic, OnTopicMessage);
|
||||
}
|
||||
mqtt.Connect();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mockup 모드인 경우 MockMQTTService를 사용하여 테스트 환경을 설정합니다.
|
||||
mockupMQTT = new MockMQTTService();
|
||||
foreach (var topic in infoList.Keys)
|
||||
{
|
||||
mockupMQTT.AddTopicHandler(topic, OnTopicMessage);
|
||||
}
|
||||
mockupMQTT.Connect();
|
||||
}
|
||||
mqtt.Connect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -141,11 +167,19 @@ namespace UVC.Data
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
foreach (var topic in infoList.Keys)
|
||||
if (!UseMockup)
|
||||
{
|
||||
mqtt.RemoveTopicHandler(topic, OnTopicMessage);
|
||||
foreach (var topic in infoList.Keys)
|
||||
{
|
||||
mqtt.RemoveTopicHandler(topic, OnTopicMessage);
|
||||
}
|
||||
mqtt.Disconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Mockup 모드인 경우 MockMQTTService를 사용하여 연결을 종료합니다.
|
||||
mockupMQTT?.Disconnect();
|
||||
}
|
||||
mqtt.Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -155,7 +189,8 @@ namespace UVC.Data
|
||||
/// <see cref="Dispose"/>를 호출한 후에는 해당 인스턴스를 더 이상 사용할 수 없습니다.</remarks>
|
||||
public void Dispose()
|
||||
{
|
||||
mqtt.Disconnect();
|
||||
if (!UseMockup) mqtt.Disconnect();
|
||||
else mockupMQTT?.Disconnect();
|
||||
infoList.Clear();
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 게임 오브젝트의 RectTransform에 여백 적용
|
||||
/// RectTransform panelRect = panel.GetComponent<RectTransform>();
|
||||
/// RectTransform panelRect = panel.GetComponent<RectTransform>();
|
||||
/// panelRect.SetRectMargin(10f, 10f, 10f, 10f); // 사방 10픽셀 여백 설정
|
||||
///
|
||||
/// // UI 요소를 부모 컨테이너에 맞추되 여백 주기
|
||||
/// RectTransform childRect = childObject.GetComponent<RectTransform>();
|
||||
/// RectTransform childRect = childObject.GetComponent<RectTransform>();
|
||||
/// childRect.SetRectMargin(5f, 5f, 20f, 5f); // 상단에 더 큰 여백 설정
|
||||
/// </code>
|
||||
/// </example>
|
||||
@@ -44,7 +44,7 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 버튼의 너비를 200으로 설정
|
||||
/// RectTransform buttonRect = button.GetComponent<RectTransform>();
|
||||
/// RectTransform buttonRect = button.GetComponent<RectTransform>();
|
||||
/// buttonRect.SetWidth(200f);
|
||||
/// </code>
|
||||
/// </example>
|
||||
@@ -61,7 +61,7 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 패널의 높이를 150으로 설정
|
||||
/// RectTransform panelRect = panel.GetComponent<RectTransform>();
|
||||
/// RectTransform panelRect = panel.GetComponent<RectTransform>();
|
||||
/// panelRect.SetHeight(150f);
|
||||
/// </code>
|
||||
/// </example>
|
||||
@@ -78,7 +78,7 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 이미지의 크기를 100x100으로 설정
|
||||
/// RectTransform imageRect = image.GetComponent<RectTransform>();
|
||||
/// RectTransform imageRect = image.GetComponent<RectTransform>();
|
||||
/// imageRect.SetSize(new Vector2(100f, 100f));
|
||||
/// </code>
|
||||
/// </example>
|
||||
@@ -95,7 +95,7 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 버튼을 화면 중앙에 위치시키기
|
||||
/// RectTransform buttonRect = button.GetComponent<RectTransform>();
|
||||
/// RectTransform buttonRect = button.GetComponent<RectTransform>();
|
||||
/// buttonRect.SetAnchorsToCenter();
|
||||
/// buttonRect.anchoredPosition = Vector2.zero; // 중앙 위치에 배치
|
||||
/// </code>
|
||||
@@ -114,7 +114,7 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // UI 요소를 왼쪽 상단에 위치시키기
|
||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||
/// elementRect.SetAnchorsToTopLeft();
|
||||
/// elementRect.anchoredPosition = new Vector2(10f, -10f); // 약간의 여백 추가
|
||||
/// </code>
|
||||
@@ -133,7 +133,7 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 배경 이미지를 패널 전체에 채우기
|
||||
/// RectTransform backgroundRect = backgroundImage.GetComponent<RectTransform>();
|
||||
/// RectTransform backgroundRect = backgroundImage.GetComponent<RectTransform>();
|
||||
/// backgroundRect.StretchToParentEdges();
|
||||
/// </code>
|
||||
/// </example>
|
||||
@@ -150,7 +150,7 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 요소를 부모의 오른쪽 상단에 배치
|
||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||
/// elementRect.SetNormalizedPosition(new Vector2(0.95f, 0.95f));
|
||||
/// </code>
|
||||
/// </example>
|
||||
@@ -177,7 +177,7 @@ namespace UVC.Extension
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // UI 요소가 특정 월드 좌표를 포함하는지 확인
|
||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||
/// Rect worldRect = elementRect.GetWorldRect();
|
||||
/// Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||
/// bool isOverUI = worldRect.Contains(new Vector2(worldPos.x, worldPos.y));
|
||||
|
||||
@@ -116,7 +116,7 @@ namespace UVC.Extension
|
||||
/// <code>
|
||||
/// // 바이트 배열에서 객체 복원
|
||||
/// byte[] savedData = GetSavedDataFromSomewhere();
|
||||
/// Person restoredPerson = savedData.DeserializeFromBytes<Person>();
|
||||
/// Person restoredPerson = savedData.DeserializeFromBytes<Person>();
|
||||
///
|
||||
/// if (restoredPerson != null)
|
||||
/// {
|
||||
@@ -184,7 +184,7 @@ namespace UVC.Extension
|
||||
/// <returns>역직렬화된 객체 또는 실패 시 null</returns>
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// GameSettings settings = DeserializeFromFile<GameSettings>("Assets/settings.dat");
|
||||
/// GameSettings settings = DeserializeFromFile<GameSettings>("Assets/settings.dat");
|
||||
/// if (settings != null)
|
||||
/// {
|
||||
/// ApplySettings(settings.Volume, settings.Difficulty);
|
||||
|
||||
@@ -60,12 +60,12 @@ namespace UVC.Json
|
||||
/// <code>
|
||||
/// // 단일 객체 역직렬화
|
||||
/// string userJson = "{\"Id\":1,\"Name\":\"홍길동\"}";
|
||||
/// User user = JsonHelper.FromJson<User>(userJson);
|
||||
/// User user = JsonHelper.FromJson<User>(userJson);
|
||||
/// Debug.Log($"사용자 정보: {user.Id}, {user.Name}");
|
||||
///
|
||||
/// // 컬렉션 역직렬화
|
||||
/// string arrayJson = "[{\"Id\":1,\"Name\":\"홍길동\"},{\"Id\":2,\"Name\":\"김철수\"}]";
|
||||
/// List<User> users = JsonHelper.FromJson<List<User>>(arrayJson);
|
||||
/// List<User> users = JsonHelper.FromJson<List<User>>(arrayJson);
|
||||
/// foreach(var u in users) {
|
||||
/// Debug.Log($"사용자 정보: {u.Id}, {u.Name}");
|
||||
/// }
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace UVC.network
|
||||
/// <value>클라이언트가 초기화되고 브로커에 연결된 경우 <c>true</c>를 반환합니다.</value>
|
||||
public bool IsConnected => client != null && client.State == ClientStates.Connected;
|
||||
|
||||
private Action<string, string> onMessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// MQTTService 인스턴스를 생성합니다.
|
||||
@@ -126,7 +125,7 @@ namespace UVC.network
|
||||
/// <example>
|
||||
/// <code>
|
||||
/// // 핸들러 정의
|
||||
/// Action<string, string> temperatureHandler = (topic, message) => {
|
||||
/// Action<string, string> temperatureHandler = (topic, message) => {
|
||||
/// Debug.Log($"온도 데이터 수신: {message}");
|
||||
/// };
|
||||
///
|
||||
@@ -324,9 +323,11 @@ namespace UVC.network
|
||||
string payload = Encoding.UTF8.GetString(message.Payload.Data, message.Payload.Offset, message.Payload.Count);
|
||||
Debug.Log($"MQTT OnTopic {topic.Filter.OriginalFilter} => {payload}");
|
||||
ServerLog.LogMqtt(MQTTDomain, MQTTPort.ToString(), topic.Filter.OriginalFilter, payload, DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"));
|
||||
if (onMessageReceived != null)
|
||||
|
||||
if(topicHandler.TryGetValue(topic.Filter.OriginalFilter, out var handler))
|
||||
{
|
||||
onMessageReceived.Invoke(topic.Filter.OriginalFilter, payload);
|
||||
// 등록된 핸들러가 있으면 호출
|
||||
handler.Invoke(topic.Filter.OriginalFilter, payload);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
602
Assets/Scripts/UVC/Tests/Data/MQTTPipeLineTests.cs
Normal file
602
Assets/Scripts/UVC/Tests/Data/MQTTPipeLineTests.cs
Normal file
@@ -0,0 +1,602 @@
|
||||
#nullable enable
|
||||
|
||||
using Cysharp.Threading.Tasks;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NUnit.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UVC.Data;
|
||||
|
||||
namespace UVC.Tests.Data
|
||||
{
|
||||
[TestFixture]
|
||||
public class MQTTPipeLineTests
|
||||
{
|
||||
private MQTTPipeLine pipeLine;
|
||||
private Dictionary<string, TestDataHandler> handlers;
|
||||
private Dictionary<string, DataMask> dataMasks;
|
||||
private readonly string[] topicNames = { "AGV", "CARRIER", "STOCKER_STACK", "ALL" };
|
||||
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
// 기본 테스트 환경 설정
|
||||
pipeLine = new MQTTPipeLine("localhost", 1883);
|
||||
pipeLine.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 TestAll()
|
||||
{
|
||||
Setup();
|
||||
Debug.Log("===== MQTTPipeLine 테스트 시작 =====");
|
||||
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);
|
||||
Debug.Log("===== MQTTPipeLine 테스트 완료 =====");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void TearDown()
|
||||
{
|
||||
// 테스트 완료 후 리소스 정리
|
||||
pipeLine.Stop();
|
||||
pipeLine.Dispose();
|
||||
}
|
||||
|
||||
private DataMask CreateDataMaskForTopic(string topic)
|
||||
{
|
||||
var dataMask = new DataMask();
|
||||
dataMask.ObjectName = topic;
|
||||
|
||||
// 각 토픽별로 ObjectIdKey 설정
|
||||
switch (topic)
|
||||
{
|
||||
case "AGV":
|
||||
dataMask.ObjectIdKey = "VHL_NAME";
|
||||
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["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["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 JArray
|
||||
{
|
||||
new DataObject {
|
||||
["STOCKER_NAME"] = "HFF09AGN0300",
|
||||
["STEP_ID"] = "8106",
|
||||
["RACK_STEP_COUNT"] = "88",
|
||||
["TOTAL"] = "834",
|
||||
["STEP_CAPACITY"] = "10.55",
|
||||
},
|
||||
};
|
||||
break;
|
||||
case "ALL":
|
||||
// ALL 토픽은 ObjectIdKey 없음
|
||||
dataMask["AGV"] = new JArray()
|
||||
{
|
||||
new DataObject
|
||||
{
|
||||
["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 JArray()
|
||||
{
|
||||
new DataObject
|
||||
{
|
||||
["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 JArray()
|
||||
{
|
||||
new DataObject
|
||||
{
|
||||
["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 JArray
|
||||
{
|
||||
new DataObject {
|
||||
["STOCKER_NAME"] = "HFF09AGN0300",
|
||||
["STEP_ID"] = "8106",
|
||||
["RACK_STEP_COUNT"] = "88",
|
||||
["TOTAL"] = "834",
|
||||
["STEP_CAPACITY"] = "10.55",
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
return dataMask;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async UniTask ExecutePipeLine_AllTopics_RegistersAndHandlesMessages()
|
||||
{
|
||||
// Arrange - 파이프라인 설정
|
||||
foreach (var topic in topicNames)
|
||||
{
|
||||
// 필요한 UpdatedDataOnly 설정
|
||||
bool updatedDataOnly = topic != "ALL";
|
||||
|
||||
var pipelineInfo = new MQTTPipeLineInfo(topic, updatedDataOnly)
|
||||
.setDataMapper(new DataMapper(dataMasks[topic]))
|
||||
.setHandler(handlers[topic].HandleData);
|
||||
|
||||
pipeLine.Add(pipelineInfo);
|
||||
}
|
||||
|
||||
// Act - 파이프라인 실행
|
||||
pipeLine.Execute();
|
||||
|
||||
// Assert - 일정 시간 기다린 후 각 핸들러가 호출되었는지 확인
|
||||
await UniTask.Delay(1500);
|
||||
|
||||
// 각 토픽별로 핸들러가 호출되었는지 확인
|
||||
foreach (var topic in topicNames)
|
||||
{
|
||||
Assert.IsTrue(handlers[topic].CallCount > 0, $"{topic} 토픽의 핸들러가 호출되지 않았습니다.");
|
||||
|
||||
if (topic != "ALL")
|
||||
{
|
||||
// ALL을 제외한 토픽은 ObjectIdKey가 설정되어 있어야 함
|
||||
Assert.IsNotNull(handlers[topic].LastDataObject, $"{topic} 토픽의 핸들러에 전달된 DataObject가 null입니다.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async UniTask RemoveTopic_ShouldStopReceivingMessages()
|
||||
{
|
||||
// Arrange
|
||||
// AGV 토픽만 등록
|
||||
var agvInfo = new MQTTPipeLineInfo("AGV", true)
|
||||
.setDataMapper(new DataMapper(dataMasks["AGV"]))
|
||||
.setHandler(handlers["AGV"].HandleData);
|
||||
|
||||
pipeLine.Add(agvInfo);
|
||||
pipeLine.Execute();
|
||||
|
||||
// 메시지가 수신되도록 잠시 대기
|
||||
await UniTask.Delay(1000);
|
||||
|
||||
// 초기 호출 횟수 저장
|
||||
int initialCallCount = handlers["AGV"].CallCount;
|
||||
Assert.IsTrue(initialCallCount > 0, "초기 AGV 토픽의 핸들러가 호출되지 않았습니다.");
|
||||
|
||||
// Act
|
||||
pipeLine.Remove("AGV"); // AGV 토픽 제거
|
||||
|
||||
// 핸들러 초기화
|
||||
handlers["AGV"].Reset();
|
||||
|
||||
// 충분한 시간 대기
|
||||
await UniTask.Delay(1500);
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, handlers["AGV"].CallCount, "토픽을 제거했지만 핸들러가 여전히 호출되고 있습니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async UniTask UpdatedDataOnly_ShouldOnlyCallHandlerForUpdatedData()
|
||||
{
|
||||
// Arrange - 파이프라인 설정
|
||||
// TestMQTTPipeLine을 사용하여 직접 메시지를 보낼 수 있게 함
|
||||
var testPipeLine = new TestMQTTPipeLine();
|
||||
|
||||
// UpdatedDataOnly가 true인 AGV 토픽 추가
|
||||
var agvInfo = new MQTTPipeLineInfo("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가 필요하므로 완전한 테스트는 어려움
|
||||
// 여기서는 최소한 첫 번째 메시지가 처리되었는지 확인
|
||||
Assert.IsTrue(handlers["AGV"].CallCount > 0, "AGV 토픽의 핸들러가 호출되지 않았습니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnTopicMessage_ValidJsonObject_CallsHandler()
|
||||
{
|
||||
// Arrange
|
||||
var testPipeLine = new TestMQTTPipeLine();
|
||||
|
||||
foreach (var topic in topicNames)
|
||||
{
|
||||
bool updatedDataOnly = topic != "ALL";
|
||||
var pipelineInfo = new MQTTPipeLineInfo(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)
|
||||
{
|
||||
Assert.AreEqual(1, handlers[topic].CallCount, $"{topic} 토픽의 핸들러가 정확히 한 번 호출되어야 합니다.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnTopicMessage_JsonArray_CallsHandler()
|
||||
{
|
||||
// Arrange
|
||||
var testPipeLine = new TestMQTTPipeLine();
|
||||
|
||||
var pipelineInfo = new MQTTPipeLineInfo("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
|
||||
Assert.AreEqual(1, handlers["AGV"].CallCount, "AGV 토픽의 핸들러가 정확히 한 번 호출되어야 합니다.");
|
||||
Assert.IsNotNull(handlers["AGV"].LastDataObject, "핸들러에 전달된 DataObject가 null입니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnTopicMessage_EmptyMessage_DoesNotCallHandler()
|
||||
{
|
||||
// Arrange
|
||||
var testPipeLine = new TestMQTTPipeLine();
|
||||
|
||||
var pipelineInfo = new MQTTPipeLineInfo("AGV", true)
|
||||
.setDataMapper(new DataMapper(dataMasks["AGV"]))
|
||||
.setHandler(handlers["AGV"].HandleData);
|
||||
|
||||
testPipeLine.Add(pipelineInfo);
|
||||
|
||||
// Act
|
||||
testPipeLine.TestOnTopicMessage("AGV", ""); // 빈 메시지 전송
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, handlers["AGV"].CallCount, "빈 메시지가 전달되었을 때 핸들러가 호출되지 않아야 합니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void OnTopicMessage_InvalidJson_DoesNotCallHandler()
|
||||
{
|
||||
// Arrange
|
||||
var testPipeLine = new TestMQTTPipeLine();
|
||||
|
||||
var pipelineInfo = new MQTTPipeLineInfo("AGV", true)
|
||||
.setDataMapper(new DataMapper(dataMasks["AGV"]))
|
||||
.setHandler(handlers["AGV"].HandleData);
|
||||
|
||||
testPipeLine.Add(pipelineInfo);
|
||||
|
||||
// Act - 잘못된 JSON 형식의 메시지 전송
|
||||
testPipeLine.TestOnTopicMessage("AGV", "{invalid json}");
|
||||
|
||||
// Assert
|
||||
Assert.AreEqual(0, handlers["AGV"].CallCount, "잘못된 JSON 형식의 메시지가 전달되었을 때 핸들러가 호출되지 않아야 합니다.");
|
||||
}
|
||||
|
||||
[Test]
|
||||
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. MQTTPipeLine 설정 (MockMQTTService 사용)
|
||||
var pipeline = new MQTTPipeLine("localhost", 1883);
|
||||
pipeline.UseMockup = true; // MockMQTTService 사용 설정
|
||||
|
||||
// 4. UpdatedDataOnly=true로 토픽 등록
|
||||
var pipelineInfo = new MQTTPipeLineInfo("AGV", true)
|
||||
.setDataMapper(new DataMapper(dataMask))
|
||||
.setHandler(handler.HandleData);
|
||||
|
||||
pipeline.Add(pipelineInfo);
|
||||
|
||||
// Act
|
||||
// 파이프라인 실행 - 이것이 MockMQTTService를 통해 메시지를 보내기 시작
|
||||
pipeline.Execute();
|
||||
|
||||
// 첫 번째 데이터 세트가 수신될 때까지 대기
|
||||
await UniTask.Delay(1500);
|
||||
|
||||
// 첫 번째 데이터 세트의 콜백 수를 기록
|
||||
int initialCallCount = handler.CallCount;
|
||||
Assert.IsTrue(initialCallCount > 0, "첫 번째 메시지 세트가 수신되지 않았습니다.");
|
||||
|
||||
// AGV 항목 수 저장
|
||||
int initialAgvCount = handler.ReceivedAgvItems.Count;
|
||||
Assert.IsTrue(initialAgvCount > 0, "첫 번째 메시지에 AGV 항목이 없습니다.");
|
||||
|
||||
// 첫 번째 콜백에서 수신된 업데이트 항목 수 기록
|
||||
int firstUpdatedCount = handler.LastUpdatedCount;
|
||||
Assert.IsTrue(firstUpdatedCount > 0, "첫 번째 메시지에 업데이트된 데이터가 없습니다.");
|
||||
|
||||
// 다음 데이터 세트가 도착하기를 기다림
|
||||
await UniTask.Delay(1500);
|
||||
|
||||
// 두 번째 메시지가 도착했는지 확인
|
||||
int finalCallCount = handler.CallCount;
|
||||
Assert.IsTrue(finalCallCount > initialCallCount, "두 번째 메시지 세트가 수신되지 않았습니다.");
|
||||
|
||||
// Assert
|
||||
// 기본 검사: 모든 호출에서 업데이트된 데이터만 전송되었는지 확인
|
||||
foreach (int updatedCount in handler.UpdatedCounts)
|
||||
{
|
||||
Assert.IsTrue(updatedCount > 0, "업데이트된 데이터가 없는 콜백이 있습니다.");
|
||||
}
|
||||
|
||||
// 두 번째 메시지에서 처음 도착한 AGV 항목과 추가/변경된 항목이 있는지 확인
|
||||
// MockMQTTService는 매번 다른 데이터 세트를 보내므로 이런 차이가 있어야 함
|
||||
bool hasChanges = handler.ReceivedAgvItems.Count > initialAgvCount ||
|
||||
handler.HasUpdatedExistingItems;
|
||||
|
||||
Assert.IsTrue(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++;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MQTTPipeLine의 OnTopicMessage 메서드를 테스트하기 위한 확장 클래스
|
||||
public class TestMQTTPipeLine : MQTTPipeLine
|
||||
{
|
||||
public TestMQTTPipeLine() : base("localhost", 1883)
|
||||
{
|
||||
UseMockup = true;
|
||||
}
|
||||
|
||||
public void TestOnTopicMessage(string topic, string message)
|
||||
{
|
||||
// private 메서드에 접근하기 위한 래퍼
|
||||
typeof(MQTTPipeLine).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;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/UVC/Tests/Data/MQTTPipeLineTests.cs.meta
Normal file
2
Assets/Scripts/UVC/Tests/Data/MQTTPipeLineTests.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d12a5bc2caf7f440b9a294cdf18cb4a
|
||||
173
Assets/Scripts/UVC/Tests/MockMQTTService.cs
Normal file
173
Assets/Scripts/UVC/Tests/MockMQTTService.cs
Normal file
File diff suppressed because one or more lines are too long
2
Assets/Scripts/UVC/Tests/MockMQTTService.cs.meta
Normal file
2
Assets/Scripts/UVC/Tests/MockMQTTService.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ed55ad7bcf744a34b9da619859525801
|
||||
@@ -7,7 +7,8 @@ namespace UVC.Tests
|
||||
public static void RunAllTests()
|
||||
{
|
||||
//new DataMapperTests().TestAll();
|
||||
new HttpPipeLineTests().TestAll();
|
||||
//new HttpPipeLineTests().TestAll();
|
||||
new MQTTPipeLineTests().TestAll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user