MQTTPipeLineTests test 중
This commit is contained in:
@@ -223,7 +223,7 @@ namespace SQLite4Unity3d
|
|||||||
/// var db = new SQLiteConnection(Application.persistentDataPath + "/myDatabase.db", true);
|
/// var db = new SQLiteConnection(Application.persistentDataPath + "/myDatabase.db", true);
|
||||||
///
|
///
|
||||||
/// // 테이블 생성
|
/// // 테이블 생성
|
||||||
/// db.CreateTable<Person>();
|
/// db.CreateTable<Person>();
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = false)
|
public SQLiteConnection(string databasePath, bool storeDateTimeAsTicks = false)
|
||||||
@@ -438,7 +438,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 제네릭 타입으로 매핑 얻기
|
/// // 제네릭 타입으로 매핑 얻기
|
||||||
/// var mapping = db.GetMapping<Person>();
|
/// var mapping = db.GetMapping<Person>();
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public TableMapping GetMapping<T>()
|
public TableMapping GetMapping<T>()
|
||||||
@@ -466,7 +466,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // Person 테이블 삭제
|
/// // Person 테이블 삭제
|
||||||
/// db.DropTable<Person>();
|
/// db.DropTable<Person>();
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public int DropTable<T>()
|
public int DropTable<T>()
|
||||||
@@ -490,10 +490,10 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // Person 클래스를 표현하는 테이블 생성
|
/// // Person 클래스를 표현하는 테이블 생성
|
||||||
/// db.CreateTable<Person>();
|
/// db.CreateTable<Person>();
|
||||||
///
|
///
|
||||||
/// // 암시적 인덱스 생성 옵션 사용
|
/// // 암시적 인덱스 생성 옵션 사용
|
||||||
/// db.CreateTable<Person>(CreateFlags.ImplicitIndex);
|
/// db.CreateTable<Person>(CreateFlags.ImplicitIndex);
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public int CreateTable<T>(CreateFlags createFlags = CreateFlags.None)
|
public int CreateTable<T>(CreateFlags createFlags = CreateFlags.None)
|
||||||
@@ -675,7 +675,7 @@ namespace SQLite4Unity3d
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 지정된 객체 속성에 대한 인덱스를 생성합니다.
|
/// 지정된 객체 속성에 대한 인덱스를 생성합니다.
|
||||||
/// 예시: CreateIndex<Client>(c => c.Name);
|
/// 예시: CreateIndex<Client>(c => c.Name);
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">데이터베이스 테이블에 반영할 타입.</typeparam>
|
/// <typeparam name="T">데이터베이스 테이블에 반영할 타입.</typeparam>
|
||||||
/// <param name="property">인덱싱할 속성</param>
|
/// <param name="property">인덱싱할 속성</param>
|
||||||
@@ -683,7 +683,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 람다 식을 사용하여 속성에 대한 인덱스 생성
|
/// // 람다 식을 사용하여 속성에 대한 인덱스 생성
|
||||||
/// db.CreateIndex<Person>(p => p.Email, true);
|
/// db.CreateIndex<Person>(p => p.Email, true);
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public void CreateIndex<T>(Expression<Func<T, object>> property, bool unique = false)
|
public void CreateIndex<T>(Expression<Func<T, object>> property, bool unique = false)
|
||||||
@@ -821,7 +821,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <code>
|
/// <code>
|
||||||
/// // 파라미터가 있는 명령 생성
|
/// // 파라미터가 있는 명령 생성
|
||||||
/// var cmd = db.CreateCommand("SELECT * FROM Person WHERE Id = ?", 1);
|
/// var cmd = db.CreateCommand("SELECT * FROM Person WHERE Id = ?", 1);
|
||||||
/// var person = cmd.ExecuteQuery<Person>().FirstOrDefault();
|
/// var person = cmd.ExecuteQuery<Person>().FirstOrDefault();
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public SQLiteCommand CreateCommand(string cmdText, params object[] ps)
|
public SQLiteCommand CreateCommand(string cmdText, params object[] ps)
|
||||||
@@ -897,8 +897,8 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 스칼라 값 가져오기
|
/// // 스칼라 값 가져오기
|
||||||
/// int count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
/// int count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||||
/// string name = db.ExecuteScalar<string>("SELECT Name FROM Person WHERE Id = ?", 1);
|
/// string name = db.ExecuteScalar<string>("SELECT Name FROM Person WHERE Id = ?", 1);
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public T ExecuteScalar<T>(string query, params object[] args)
|
public T ExecuteScalar<T>(string query, params object[] args)
|
||||||
@@ -944,7 +944,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 조건에 맞는 모든 Person 객체 조회
|
/// // 조건에 맞는 모든 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) {
|
/// foreach (var person in adults) {
|
||||||
/// Console.WriteLine($"성인: {person.Name}, {person.Age}세");
|
/// Console.WriteLine($"성인: {person.Name}, {person.Age}세");
|
||||||
/// }
|
/// }
|
||||||
@@ -975,7 +975,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 대용량 데이터를 지연 로딩으로 처리
|
/// // 대용량 데이터를 지연 로딩으로 처리
|
||||||
/// var query = db.DeferredQuery<Person>("SELECT * FROM Person");
|
/// var query = db.DeferredQuery<Person>("SELECT * FROM Person");
|
||||||
/// using (var enumerator = query.GetEnumerator()) {
|
/// using (var enumerator = query.GetEnumerator()) {
|
||||||
/// while (enumerator.MoveNext()) {
|
/// while (enumerator.MoveNext()) {
|
||||||
/// var person = enumerator.Current;
|
/// var person = enumerator.Current;
|
||||||
@@ -1061,7 +1061,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // LINQ를 사용하여 데이터 쿼리하기
|
/// // LINQ를 사용하여 데이터 쿼리하기
|
||||||
/// var query = db.Table<Person>()
|
/// var query = db.Table<Person>()
|
||||||
/// .Where(p => p.Age > 18)
|
/// .Where(p => p.Age > 18)
|
||||||
/// .OrderBy(p => p.Name);
|
/// .OrderBy(p => p.Name);
|
||||||
///
|
///
|
||||||
@@ -1091,7 +1091,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <code>
|
/// <code>
|
||||||
/// // 기본 키로 Person 객체 가져오기
|
/// // 기본 키로 Person 객체 가져오기
|
||||||
/// try {
|
/// try {
|
||||||
/// var person = db.Get<Person>(1);
|
/// var person = db.Get<Person>(1);
|
||||||
/// Console.WriteLine($"이름: {person.Name}");
|
/// Console.WriteLine($"이름: {person.Name}");
|
||||||
/// } catch (Exception) {
|
/// } catch (Exception) {
|
||||||
/// Console.WriteLine("해당 ID의 Person을 찾을 수 없습니다");
|
/// Console.WriteLine("해당 ID의 Person을 찾을 수 없습니다");
|
||||||
@@ -1118,7 +1118,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <code>
|
/// <code>
|
||||||
/// // 조건식으로 Person 객체 가져오기
|
/// // 조건식으로 Person 객체 가져오기
|
||||||
/// try {
|
/// 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}");
|
/// Console.WriteLine($"이름: {person.Name}");
|
||||||
/// } catch (Exception) {
|
/// } catch (Exception) {
|
||||||
/// Console.WriteLine("조건에 맞는 Person을 찾을 수 없습니다");
|
/// Console.WriteLine("조건에 맞는 Person을 찾을 수 없습니다");
|
||||||
@@ -1145,7 +1145,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 기본 키로 Person 객체 찾기 (없으면 null 반환)
|
/// // 기본 키로 Person 객체 찾기 (없으면 null 반환)
|
||||||
/// var person = db.Find<Person>(1);
|
/// var person = db.Find<Person>(1);
|
||||||
/// if (person != null) {
|
/// if (person != null) {
|
||||||
/// Console.WriteLine($"찾았습니다: {person.Name}");
|
/// Console.WriteLine($"찾았습니다: {person.Name}");
|
||||||
/// } else {
|
/// } else {
|
||||||
@@ -1203,7 +1203,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 조건식으로 Person 객체 찾기 (없으면 null 반환)
|
/// // 조건식으로 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) {
|
/// if (person != null) {
|
||||||
/// Console.WriteLine($"찾았습니다: {person.Name}");
|
/// Console.WriteLine($"찾았습니다: {person.Name}");
|
||||||
/// } else {
|
/// } else {
|
||||||
@@ -1616,10 +1616,10 @@ namespace SQLite4Unity3d
|
|||||||
/// // RunInDatabaseLock을 사용한 스레드 안전한 데이터베이스 접근
|
/// // RunInDatabaseLock을 사용한 스레드 안전한 데이터베이스 접근
|
||||||
/// db.RunInDatabaseLock(() => {
|
/// db.RunInDatabaseLock(() => {
|
||||||
/// // 이 블록 내의 작업은 다른 스레드에서 동일한 데이터베이스에 접근할 수 없습니다.
|
/// // 이 블록 내의 작업은 다른 스레드에서 동일한 데이터베이스에 접근할 수 없습니다.
|
||||||
/// var count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
/// var count = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||||
///
|
///
|
||||||
/// if (count > 0) {
|
/// if (count > 0) {
|
||||||
/// var people = db.Query<Person>("SELECT * FROM Person");
|
/// var people = db.Query<Person>("SELECT * FROM Person");
|
||||||
/// foreach (var person in people) {
|
/// foreach (var person in people) {
|
||||||
/// // 안전하게 데이터 처리
|
/// // 안전하게 데이터 처리
|
||||||
/// ProcessPerson(person);
|
/// ProcessPerson(person);
|
||||||
@@ -1648,7 +1648,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 객체 목록을 한 번에 삽입
|
/// // 객체 목록을 한 번에 삽입
|
||||||
/// var people = new List<Person>
|
/// var people = new List<Person>
|
||||||
/// {
|
/// {
|
||||||
/// new Person { Name = "홍길동", Age = 30 },
|
/// new Person { Name = "홍길동", Age = 30 },
|
||||||
/// new Person { Name = "김철수", Age = 25 },
|
/// new Person { Name = "김철수", Age = 25 },
|
||||||
@@ -1687,7 +1687,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 여러 객체 한 번에 삽입 (충돌 무시 옵션 사용)
|
/// // 여러 객체 한 번에 삽입 (충돌 무시 옵션 사용)
|
||||||
/// var people = new List<Person>
|
/// var people = new List<Person>
|
||||||
/// {
|
/// {
|
||||||
/// new Person { Id = 1, Name = "홍길동", Age = 30 },
|
/// new Person { Id = 1, Name = "홍길동", Age = 30 },
|
||||||
/// new Person { Id = 2, Name = "김철수", Age = 25 },
|
/// new Person { Id = 2, Name = "김철수", Age = 25 },
|
||||||
@@ -1726,7 +1726,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 동적 타입으로 여러 객체 한 번에 삽입
|
/// // 동적 타입으로 여러 객체 한 번에 삽입
|
||||||
/// var people = new List<object>
|
/// var people = new List<object>
|
||||||
/// {
|
/// {
|
||||||
/// new Person { Name = "홍길동", Age = 30 },
|
/// new Person { Name = "홍길동", Age = 30 },
|
||||||
/// new Person { Name = "김철수", Age = 25 }
|
/// new Person { Name = "김철수", Age = 25 }
|
||||||
@@ -1798,7 +1798,7 @@ namespace SQLite4Unity3d
|
|||||||
/// db.InsertOrReplace(new Person { Id = 1, Name = "홍길동(수정)", Age = 31 });
|
/// db.InsertOrReplace(new Person { Id = 1, Name = "홍길동(수정)", Age = 31 });
|
||||||
///
|
///
|
||||||
/// // 값이 변경된 객체를 교체 삽입하는 방식으로 업데이트할 수도 있음
|
/// // 값이 변경된 객체를 교체 삽입하는 방식으로 업데이트할 수도 있음
|
||||||
/// var person = db.Get<Person>(1);
|
/// var person = db.Get<Person>(1);
|
||||||
/// person.Age += 1;
|
/// person.Age += 1;
|
||||||
/// db.InsertOrReplace(person);
|
/// db.InsertOrReplace(person);
|
||||||
/// </code>
|
/// </code>
|
||||||
@@ -2027,7 +2027,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 데이터베이스에서 객체 검색
|
/// // 데이터베이스에서 객체 검색
|
||||||
/// var person = db.Get<Person>(1);
|
/// var person = db.Get<Person>(1);
|
||||||
///
|
///
|
||||||
/// // 객체 속성 변경
|
/// // 객체 속성 변경
|
||||||
/// person.Name = "홍길동(수정됨)";
|
/// person.Name = "홍길동(수정됨)";
|
||||||
@@ -2127,7 +2127,7 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <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) {
|
/// foreach (var person in people) {
|
||||||
/// person.Age++;
|
/// person.Age++;
|
||||||
/// }
|
/// }
|
||||||
@@ -2161,14 +2161,14 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 데이터베이스에서 객체 검색
|
/// // 데이터베이스에서 객체 검색
|
||||||
/// var person = db.Get<Person>(1);
|
/// var person = db.Get<Person>(1);
|
||||||
///
|
///
|
||||||
/// // 객체 삭제
|
/// // 객체 삭제
|
||||||
/// int rowsAffected = db.Delete(person);
|
/// int rowsAffected = db.Delete(person);
|
||||||
/// Console.WriteLine($"삭제된 행 수: {rowsAffected}");
|
/// Console.WriteLine($"삭제된 행 수: {rowsAffected}");
|
||||||
///
|
///
|
||||||
/// // 또는 직접 ID로 삭제
|
/// // 또는 직접 ID로 삭제
|
||||||
/// rowsAffected = db.Delete<Person>(2);
|
/// rowsAffected = db.Delete<Person>(2);
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public int Delete(object objectToDelete)
|
public int Delete(object objectToDelete)
|
||||||
@@ -2198,13 +2198,13 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 기본 키로 직접 객체 삭제
|
/// // 기본 키로 직접 객체 삭제
|
||||||
/// int rowsDeleted = db.Delete<Person>(1);
|
/// int rowsDeleted = db.Delete<Person>(1);
|
||||||
/// Console.WriteLine($"ID가 1인 Person 객체 {rowsDeleted}개 삭제됨");
|
/// Console.WriteLine($"ID가 1인 Person 객체 {rowsDeleted}개 삭제됨");
|
||||||
///
|
///
|
||||||
/// // 여러 ID를 차례로 삭제
|
/// // 여러 ID를 차례로 삭제
|
||||||
/// int[] idsToDelete = { 2, 3, 4 };
|
/// int[] idsToDelete = { 2, 3, 4 };
|
||||||
/// foreach (int id in idsToDelete) {
|
/// foreach (int id in idsToDelete) {
|
||||||
/// db.Delete<Person>(id);
|
/// db.Delete<Person>(id);
|
||||||
/// }
|
/// }
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
@@ -2237,11 +2237,11 @@ namespace SQLite4Unity3d
|
|||||||
/// db.BeginTransaction();
|
/// db.BeginTransaction();
|
||||||
/// try {
|
/// try {
|
||||||
/// // 삭제 전 확인을 위한 카운트
|
/// // 삭제 전 확인을 위한 카운트
|
||||||
/// int beforeCount = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
/// int beforeCount = db.ExecuteScalar<int>("SELECT COUNT(*) FROM Person");
|
||||||
/// Console.WriteLine($"삭제 전 데이터 수: {beforeCount}");
|
/// Console.WriteLine($"삭제 전 데이터 수: {beforeCount}");
|
||||||
///
|
///
|
||||||
/// // 모든 Person 데이터 삭제
|
/// // 모든 Person 데이터 삭제
|
||||||
/// int deleted = db.DeleteAll<Person>();
|
/// int deleted = db.DeleteAll<Person>();
|
||||||
///
|
///
|
||||||
/// Console.WriteLine($"삭제된 데이터 수: {deleted}");
|
/// Console.WriteLine($"삭제된 데이터 수: {deleted}");
|
||||||
/// db.Commit();
|
/// db.Commit();
|
||||||
@@ -2272,7 +2272,7 @@ namespace SQLite4Unity3d
|
|||||||
/// using (var db = new SQLiteConnection("database.db"))
|
/// 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() 호출됨
|
/// // using 블록이 끝나면 자동으로 db.Dispose() 호출됨
|
||||||
/// }
|
/// }
|
||||||
@@ -2282,7 +2282,7 @@ namespace SQLite4Unity3d
|
|||||||
/// try
|
/// try
|
||||||
/// {
|
/// {
|
||||||
/// // 작업 수행
|
/// // 작업 수행
|
||||||
/// connection.CreateTable<Person>();
|
/// connection.CreateTable<Person>();
|
||||||
/// }
|
/// }
|
||||||
/// finally
|
/// finally
|
||||||
/// {
|
/// {
|
||||||
@@ -2312,7 +2312,7 @@ namespace SQLite4Unity3d
|
|||||||
/// // 또는 using 문을 사용하여 자동으로 닫기
|
/// // 또는 using 문을 사용하여 자동으로 닫기
|
||||||
/// using (var db = new SQLiteConnection("database.db")) {
|
/// 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}");
|
/// Console.WriteLine($"Person 테이블의 행 수: {count}");
|
||||||
///
|
///
|
||||||
/// // using 블록이 끝나면 자동으로 db.Dispose()가 호출되어 연결이 닫힘
|
/// // using 블록이 끝나면 자동으로 db.Dispose()가 호출되어 연결이 닫힘
|
||||||
@@ -3454,11 +3454,11 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 조건식으로 데이터 필터링
|
/// // 조건식으로 데이터 필터링
|
||||||
/// var adults = db.Table<Person>()
|
/// var adults = db.Table<Person>()
|
||||||
/// .Where(p => p.Age >= 18);
|
/// .Where(p => p.Age >= 18);
|
||||||
///
|
///
|
||||||
/// // 여러 조건 체이닝
|
/// // 여러 조건 체이닝
|
||||||
/// var adultMales = db.Table<Person>()
|
/// var adultMales = db.Table<Person>()
|
||||||
/// .Where(p => p.Age >= 18)
|
/// .Where(p => p.Age >= 18)
|
||||||
/// .Where(p => p.Gender == "남성");
|
/// .Where(p => p.Gender == "남성");
|
||||||
///
|
///
|
||||||
@@ -3519,10 +3519,10 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <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) {
|
/// foreach (var person in peopleByAge) {
|
||||||
/// Console.WriteLine($"{person.Name}, {person.Age}세");
|
/// Console.WriteLine($"{person.Name}, {person.Age}세");
|
||||||
@@ -3543,10 +3543,10 @@ namespace SQLite4Unity3d
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <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) {
|
/// foreach (var person in olderFirst) {
|
||||||
/// Console.WriteLine($"{person.Name}, {person.Age}세");
|
/// Console.WriteLine($"{person.Name}, {person.Age}세");
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ namespace UVC.Data
|
|||||||
/// var mapper = new DataMapper(maskJson);
|
/// var mapper = new DataMapper(maskJson);
|
||||||
/// DataObject result = mapper.Map(sourceJson);
|
/// DataObject result = mapper.Map(sourceJson);
|
||||||
/// Debug.Log(result["name"].ToString()); // "김철수"
|
/// Debug.Log(result["name"].ToString()); // "김철수"
|
||||||
/// Debug.Log(result["age"].ToObject<int>()); // 30
|
/// Debug.Log(result["age"].ToObject<int>()); // 30
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public DataObject Mapping(JObject source)
|
public DataObject Mapping(JObject source)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace UVC.Data
|
|||||||
/// mask.ObjectName = "employees"; // 데이터 객체의 이름 지정
|
/// mask.ObjectName = "employees"; // 데이터 객체의 이름 지정
|
||||||
///
|
///
|
||||||
/// // 필드 이름 변환 규칙 설정
|
/// // 필드 이름 변환 규칙 설정
|
||||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||||
/// {
|
/// {
|
||||||
/// { "full_name", "name" }, // JSON의 full_name을 name으로 변환
|
/// { "full_name", "name" }, // JSON의 full_name을 name으로 변환
|
||||||
/// { "employee_age", "age" } // JSON의 employee_age를 age로 변환
|
/// { "employee_age", "age" } // JSON의 employee_age를 age로 변환
|
||||||
@@ -87,7 +87,7 @@ namespace UVC.Data
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// var mask = new DataMask();
|
/// var mask = new DataMask();
|
||||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||||
/// {
|
/// {
|
||||||
/// { "first_name", "firstName" },
|
/// { "first_name", "firstName" },
|
||||||
/// { "last_name", "lastName" },
|
/// { "last_name", "lastName" },
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ namespace UVC.Data
|
|||||||
/// mask.ObjectName = "users";
|
/// mask.ObjectName = "users";
|
||||||
///
|
///
|
||||||
/// // 필드 이름 변환 설정
|
/// // 필드 이름 변환 설정
|
||||||
/// mask.NamesForReplace = new Dictionary<string, string>
|
/// mask.NamesForReplace = new Dictionary<string, string>
|
||||||
/// {
|
/// {
|
||||||
/// { "userName", "name" },
|
/// { "userName", "name" },
|
||||||
/// { "userEmail", "email" }
|
/// { "userEmail", "email" }
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq;
|
|||||||
using SampleProject.Config;
|
using SampleProject.Config;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UVC.network;
|
using UVC.network;
|
||||||
|
using UVC.Tests;
|
||||||
|
|
||||||
namespace UVC.Data
|
namespace UVC.Data
|
||||||
{
|
{
|
||||||
@@ -17,6 +18,16 @@ namespace UVC.Data
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public class MQTTPipeLine
|
public class MQTTPipeLine
|
||||||
{
|
{
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 테스트를 위한 목업 모드 활성화 여부를 설정하거나 가져옵니다.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// true로 설정하면 실제 MQTT 요청 대신 MQTTPipeLine를 사용합니다.
|
||||||
|
/// 테스트 환경에서 외부 의존성 없이 MQTT 통신을 시뮬레이션할 때 유용합니다.
|
||||||
|
/// </remarks>
|
||||||
|
public bool UseMockup { get; internal set; } = false;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MQTT 브로커의 도메인 주소
|
/// MQTT 브로커의 도메인 주소
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -37,6 +48,8 @@ namespace UVC.Data
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private MQTTService mqtt;
|
private MQTTService mqtt;
|
||||||
|
|
||||||
|
private MockMQTTService? mockupMQTT;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MQTTPipeLine 인스턴스를 생성합니다.
|
/// MQTTPipeLine 인스턴스를 생성합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -85,6 +98,8 @@ namespace UVC.Data
|
|||||||
/// 파이프라인을 실행하여 MQTT 브로커에 연결하고 등록된 모든 토픽을 구독합니다.
|
/// 파이프라인을 실행하여 MQTT 브로커에 연결하고 등록된 모든 토픽을 구독합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Execute()
|
public void Execute()
|
||||||
|
{
|
||||||
|
if (!UseMockup)
|
||||||
{
|
{
|
||||||
foreach (var topic in infoList.Keys)
|
foreach (var topic in infoList.Keys)
|
||||||
{
|
{
|
||||||
@@ -92,6 +107,17 @@ namespace UVC.Data
|
|||||||
}
|
}
|
||||||
mqtt.Connect();
|
mqtt.Connect();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Mockup 모드인 경우 MockMQTTService를 사용하여 테스트 환경을 설정합니다.
|
||||||
|
mockupMQTT = new MockMQTTService();
|
||||||
|
foreach (var topic in infoList.Keys)
|
||||||
|
{
|
||||||
|
mockupMQTT.AddTopicHandler(topic, OnTopicMessage);
|
||||||
|
}
|
||||||
|
mockupMQTT.Connect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MQTT 토픽으로 메시지가 수신되었을 때 호출되는 콜백 메서드입니다.
|
/// MQTT 토픽으로 메시지가 수신되었을 때 호출되는 콜백 메서드입니다.
|
||||||
@@ -140,6 +166,8 @@ namespace UVC.Data
|
|||||||
/// 파이프라인을 중지하고 모든 토픽 구독을 해제한 후 MQTT 브로커와의 연결을 종료합니다.
|
/// 파이프라인을 중지하고 모든 토픽 구독을 해제한 후 MQTT 브로커와의 연결을 종료합니다.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void Stop()
|
public void Stop()
|
||||||
|
{
|
||||||
|
if (!UseMockup)
|
||||||
{
|
{
|
||||||
foreach (var topic in infoList.Keys)
|
foreach (var topic in infoList.Keys)
|
||||||
{
|
{
|
||||||
@@ -147,6 +175,12 @@ namespace UVC.Data
|
|||||||
}
|
}
|
||||||
mqtt.Disconnect();
|
mqtt.Disconnect();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Mockup 모드인 경우 MockMQTTService를 사용하여 연결을 종료합니다.
|
||||||
|
mockupMQTT?.Disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 현재 인스턴스에서 사용되는 모든 리소스를 해제하고 진행 중인 모든 작업을 중지합니다.
|
/// 현재 인스턴스에서 사용되는 모든 리소스를 해제하고 진행 중인 모든 작업을 중지합니다.
|
||||||
@@ -155,7 +189,8 @@ namespace UVC.Data
|
|||||||
/// <see cref="Dispose"/>를 호출한 후에는 해당 인스턴스를 더 이상 사용할 수 없습니다.</remarks>
|
/// <see cref="Dispose"/>를 호출한 후에는 해당 인스턴스를 더 이상 사용할 수 없습니다.</remarks>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
mqtt.Disconnect();
|
if (!UseMockup) mqtt.Disconnect();
|
||||||
|
else mockupMQTT?.Disconnect();
|
||||||
infoList.Clear();
|
infoList.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 게임 오브젝트의 RectTransform에 여백 적용
|
/// // 게임 오브젝트의 RectTransform에 여백 적용
|
||||||
/// RectTransform panelRect = panel.GetComponent<RectTransform>();
|
/// RectTransform panelRect = panel.GetComponent<RectTransform>();
|
||||||
/// panelRect.SetRectMargin(10f, 10f, 10f, 10f); // 사방 10픽셀 여백 설정
|
/// panelRect.SetRectMargin(10f, 10f, 10f, 10f); // 사방 10픽셀 여백 설정
|
||||||
///
|
///
|
||||||
/// // UI 요소를 부모 컨테이너에 맞추되 여백 주기
|
/// // UI 요소를 부모 컨테이너에 맞추되 여백 주기
|
||||||
/// RectTransform childRect = childObject.GetComponent<RectTransform>();
|
/// RectTransform childRect = childObject.GetComponent<RectTransform>();
|
||||||
/// childRect.SetRectMargin(5f, 5f, 20f, 5f); // 상단에 더 큰 여백 설정
|
/// childRect.SetRectMargin(5f, 5f, 20f, 5f); // 상단에 더 큰 여백 설정
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
@@ -44,7 +44,7 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 버튼의 너비를 200으로 설정
|
/// // 버튼의 너비를 200으로 설정
|
||||||
/// RectTransform buttonRect = button.GetComponent<RectTransform>();
|
/// RectTransform buttonRect = button.GetComponent<RectTransform>();
|
||||||
/// buttonRect.SetWidth(200f);
|
/// buttonRect.SetWidth(200f);
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
@@ -61,7 +61,7 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 패널의 높이를 150으로 설정
|
/// // 패널의 높이를 150으로 설정
|
||||||
/// RectTransform panelRect = panel.GetComponent<RectTransform>();
|
/// RectTransform panelRect = panel.GetComponent<RectTransform>();
|
||||||
/// panelRect.SetHeight(150f);
|
/// panelRect.SetHeight(150f);
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
@@ -78,7 +78,7 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 이미지의 크기를 100x100으로 설정
|
/// // 이미지의 크기를 100x100으로 설정
|
||||||
/// RectTransform imageRect = image.GetComponent<RectTransform>();
|
/// RectTransform imageRect = image.GetComponent<RectTransform>();
|
||||||
/// imageRect.SetSize(new Vector2(100f, 100f));
|
/// imageRect.SetSize(new Vector2(100f, 100f));
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
@@ -95,7 +95,7 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 버튼을 화면 중앙에 위치시키기
|
/// // 버튼을 화면 중앙에 위치시키기
|
||||||
/// RectTransform buttonRect = button.GetComponent<RectTransform>();
|
/// RectTransform buttonRect = button.GetComponent<RectTransform>();
|
||||||
/// buttonRect.SetAnchorsToCenter();
|
/// buttonRect.SetAnchorsToCenter();
|
||||||
/// buttonRect.anchoredPosition = Vector2.zero; // 중앙 위치에 배치
|
/// buttonRect.anchoredPosition = Vector2.zero; // 중앙 위치에 배치
|
||||||
/// </code>
|
/// </code>
|
||||||
@@ -114,7 +114,7 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // UI 요소를 왼쪽 상단에 위치시키기
|
/// // UI 요소를 왼쪽 상단에 위치시키기
|
||||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||||
/// elementRect.SetAnchorsToTopLeft();
|
/// elementRect.SetAnchorsToTopLeft();
|
||||||
/// elementRect.anchoredPosition = new Vector2(10f, -10f); // 약간의 여백 추가
|
/// elementRect.anchoredPosition = new Vector2(10f, -10f); // 약간의 여백 추가
|
||||||
/// </code>
|
/// </code>
|
||||||
@@ -133,7 +133,7 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 배경 이미지를 패널 전체에 채우기
|
/// // 배경 이미지를 패널 전체에 채우기
|
||||||
/// RectTransform backgroundRect = backgroundImage.GetComponent<RectTransform>();
|
/// RectTransform backgroundRect = backgroundImage.GetComponent<RectTransform>();
|
||||||
/// backgroundRect.StretchToParentEdges();
|
/// backgroundRect.StretchToParentEdges();
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
@@ -150,7 +150,7 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 요소를 부모의 오른쪽 상단에 배치
|
/// // 요소를 부모의 오른쪽 상단에 배치
|
||||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||||
/// elementRect.SetNormalizedPosition(new Vector2(0.95f, 0.95f));
|
/// elementRect.SetNormalizedPosition(new Vector2(0.95f, 0.95f));
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
@@ -177,7 +177,7 @@ namespace UVC.Extension
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // UI 요소가 특정 월드 좌표를 포함하는지 확인
|
/// // UI 요소가 특정 월드 좌표를 포함하는지 확인
|
||||||
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
/// RectTransform elementRect = element.GetComponent<RectTransform>();
|
||||||
/// Rect worldRect = elementRect.GetWorldRect();
|
/// Rect worldRect = elementRect.GetWorldRect();
|
||||||
/// Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
/// Vector3 worldPos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
|
||||||
/// bool isOverUI = worldRect.Contains(new Vector2(worldPos.x, worldPos.y));
|
/// bool isOverUI = worldRect.Contains(new Vector2(worldPos.x, worldPos.y));
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ namespace UVC.Extension
|
|||||||
/// <code>
|
/// <code>
|
||||||
/// // 바이트 배열에서 객체 복원
|
/// // 바이트 배열에서 객체 복원
|
||||||
/// byte[] savedData = GetSavedDataFromSomewhere();
|
/// byte[] savedData = GetSavedDataFromSomewhere();
|
||||||
/// Person restoredPerson = savedData.DeserializeFromBytes<Person>();
|
/// Person restoredPerson = savedData.DeserializeFromBytes<Person>();
|
||||||
///
|
///
|
||||||
/// if (restoredPerson != null)
|
/// if (restoredPerson != null)
|
||||||
/// {
|
/// {
|
||||||
@@ -184,7 +184,7 @@ namespace UVC.Extension
|
|||||||
/// <returns>역직렬화된 객체 또는 실패 시 null</returns>
|
/// <returns>역직렬화된 객체 또는 실패 시 null</returns>
|
||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// GameSettings settings = DeserializeFromFile<GameSettings>("Assets/settings.dat");
|
/// GameSettings settings = DeserializeFromFile<GameSettings>("Assets/settings.dat");
|
||||||
/// if (settings != null)
|
/// if (settings != null)
|
||||||
/// {
|
/// {
|
||||||
/// ApplySettings(settings.Volume, settings.Difficulty);
|
/// ApplySettings(settings.Volume, settings.Difficulty);
|
||||||
|
|||||||
@@ -60,12 +60,12 @@ namespace UVC.Json
|
|||||||
/// <code>
|
/// <code>
|
||||||
/// // 단일 객체 역직렬화
|
/// // 단일 객체 역직렬화
|
||||||
/// string userJson = "{\"Id\":1,\"Name\":\"홍길동\"}";
|
/// string userJson = "{\"Id\":1,\"Name\":\"홍길동\"}";
|
||||||
/// User user = JsonHelper.FromJson<User>(userJson);
|
/// User user = JsonHelper.FromJson<User>(userJson);
|
||||||
/// Debug.Log($"사용자 정보: {user.Id}, {user.Name}");
|
/// Debug.Log($"사용자 정보: {user.Id}, {user.Name}");
|
||||||
///
|
///
|
||||||
/// // 컬렉션 역직렬화
|
/// // 컬렉션 역직렬화
|
||||||
/// string arrayJson = "[{\"Id\":1,\"Name\":\"홍길동\"},{\"Id\":2,\"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) {
|
/// foreach(var u in users) {
|
||||||
/// Debug.Log($"사용자 정보: {u.Id}, {u.Name}");
|
/// Debug.Log($"사용자 정보: {u.Id}, {u.Name}");
|
||||||
/// }
|
/// }
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ namespace UVC.network
|
|||||||
/// <value>클라이언트가 초기화되고 브로커에 연결된 경우 <c>true</c>를 반환합니다.</value>
|
/// <value>클라이언트가 초기화되고 브로커에 연결된 경우 <c>true</c>를 반환합니다.</value>
|
||||||
public bool IsConnected => client != null && client.State == ClientStates.Connected;
|
public bool IsConnected => client != null && client.State == ClientStates.Connected;
|
||||||
|
|
||||||
private Action<string, string> onMessageReceived;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// MQTTService 인스턴스를 생성합니다.
|
/// MQTTService 인스턴스를 생성합니다.
|
||||||
@@ -126,7 +125,7 @@ namespace UVC.network
|
|||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
/// // 핸들러 정의
|
/// // 핸들러 정의
|
||||||
/// Action<string, string> temperatureHandler = (topic, message) => {
|
/// Action<string, string> temperatureHandler = (topic, message) => {
|
||||||
/// Debug.Log($"온도 데이터 수신: {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);
|
string payload = Encoding.UTF8.GetString(message.Payload.Data, message.Payload.Offset, message.Payload.Count);
|
||||||
Debug.Log($"MQTT OnTopic {topic.Filter.OriginalFilter} => {payload}");
|
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"));
|
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()
|
public static void RunAllTests()
|
||||||
{
|
{
|
||||||
//new DataMapperTests().TestAll();
|
//new DataMapperTests().TestAll();
|
||||||
new HttpPipeLineTests().TestAll();
|
//new HttpPipeLineTests().TestAll();
|
||||||
|
new MQTTPipeLineTests().TestAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user