auto: 2026-04-23 23:50 · scene: SampleScene · 7 files

This commit is contained in:
깃 관리자 2026-04-23 23:50:20 +09:00
parent a10c38605d
commit ccf5c1cf1e
7 changed files with 86 additions and 11 deletions

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 5d1f69485eb8e2b4db40eb9214a5e509
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 3d8b54c71c06ca5459e229a72131fb6d
PrefabImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Tests.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: cd426253ef84977439ab24888e4b8eb4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

8
Assets/Tests/Editor.meta Normal file
View File

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c52452b409b057746a10436cbfc9e04b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4e0b3a471538c044b8615276aaa65ba0
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@ -1,3 +1,4 @@
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using UnityEngine; using UnityEngine;
using UnityEditor; using UnityEditor;
@ -6,19 +7,48 @@ using UnityEditor;
/// EerieVillage BT5-Dev 2단계 — Player 근거리 공격 체계 EditMode 테스트. /// EerieVillage BT5-Dev 2단계 — Player 근거리 공격 체계 EditMode 테스트.
/// Prefab 자산의 컴포넌트 구성이 기획 04 §5-1 (근거리 공격 1종) 을 충족하는지 검증. /// Prefab 자산의 컴포넌트 구성이 기획 04 §5-1 (근거리 공격 1종) 을 충족하는지 검증.
/// Play 모드 실행 불요 — prefab YAML 직렬화 상태를 직접 검증하여 회귀 방지. /// Play 모드 실행 불요 — prefab YAML 직렬화 상태를 직접 검증하여 회귀 방지.
///
/// 2026-04-23 개정: Platformer.* 네임스페이스 직접 참조 제거 (Scripts/ 하위에 asmdef 부재로
/// 테스트 어셈블리가 Assembly-CSharp 를 참조 불가한 구조 — reflection 기반으로 전환).
/// </summary> /// </summary>
public class PlayerAttackTests public class PlayerAttackTests
{ {
const string PlayerPrefabPath = "Assets/Prefabs/Player.prefab"; const string PlayerPrefabPath = "Assets/Prefabs/Player.prefab";
const string EnemyPrefabPath = "Assets/Prefabs/Enemy.prefab"; const string EnemyPrefabPath = "Assets/Prefabs/Enemy.prefab";
// Platformer.* 는 Assembly-CSharp 에 속함. 테스트 어셈블리에서 직접 타입 참조 불가하므로
// GetComponents<Component>() + GetType().FullName 매칭으로 검증.
const string AttackHitboxType = "Platformer.Mechanics.AttackHitbox";
const string HealthType = "Platformer.Mechanics.Health";
const string PlayerControllerType = "Platformer.Mechanics.PlayerController";
const string EnemyControllerType = "Platformer.Mechanics.EnemyController";
static Component FindComponentByFullName(GameObject go, string fullName)
{
if (go == null) return null;
return go.GetComponents<Component>()
.FirstOrDefault(c => c != null && c.GetType().FullName == fullName);
}
static object GetFieldOrProperty(object obj, string memberName)
{
if (obj == null) return null;
var t = obj.GetType();
var field = t.GetField(memberName,
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
if (field != null) return field.GetValue(obj);
var prop = t.GetProperty(memberName,
System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
return prop?.GetValue(obj);
}
[Test] [Test]
public void Player_Prefab_Has_AttackHitbox_Component() public void Player_Prefab_Has_AttackHitbox_Component()
{ {
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
Assert.IsNotNull(prefab, $"Player.prefab not found at {PlayerPrefabPath}"); Assert.IsNotNull(prefab, $"Player.prefab not found at {PlayerPrefabPath}");
var hitbox = prefab.GetComponent<Platformer.Mechanics.AttackHitbox>(); var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
Assert.IsNotNull(hitbox, Assert.IsNotNull(hitbox,
"Player.prefab에 AttackHitbox 컴포넌트가 누락. " + "Player.prefab에 AttackHitbox 컴포넌트가 누락. " +
"BT5-Dev 2단계 재위임 집행분 (2026-04-23) 이 prefab YAML 에 반영되어야 함."); "BT5-Dev 2단계 재위임 집행분 (2026-04-23) 이 prefab YAML 에 반영되어야 함.");
@ -30,7 +60,7 @@ public class PlayerAttackTests
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
Assert.IsNotNull(prefab, $"Player.prefab not found at {PlayerPrefabPath}"); Assert.IsNotNull(prefab, $"Player.prefab not found at {PlayerPrefabPath}");
var health = prefab.GetComponent<Platformer.Mechanics.Health>(); var health = FindComponentByFullName(prefab, HealthType);
Assert.IsNotNull(health, "Player.prefab 에 Health 컴포넌트 누락 (템플릿 기본)."); Assert.IsNotNull(health, "Player.prefab 에 Health 컴포넌트 누락 (템플릿 기본).");
} }
@ -40,7 +70,7 @@ public class PlayerAttackTests
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
Assert.IsNotNull(prefab); Assert.IsNotNull(prefab);
var controller = prefab.GetComponent<Platformer.Mechanics.PlayerController>(); var controller = FindComponentByFullName(prefab, PlayerControllerType);
Assert.IsNotNull(controller, "Player.prefab 에 PlayerController 컴포넌트 누락."); Assert.IsNotNull(controller, "Player.prefab 에 PlayerController 컴포넌트 누락.");
} }
@ -48,10 +78,12 @@ public class PlayerAttackTests
public void AttackHitbox_Default_Damage_Is_One() public void AttackHitbox_Default_Damage_Is_One()
{ {
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
var hitbox = prefab.GetComponent<Platformer.Mechanics.AttackHitbox>(); var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
Assert.IsNotNull(hitbox); Assert.IsNotNull(hitbox);
Assert.AreEqual(1, hitbox.damage, var damage = GetFieldOrProperty(hitbox, "damage");
Assert.IsNotNull(damage, "AttackHitbox.damage 필드/프로퍼티 접근 불가");
Assert.AreEqual(1, System.Convert.ToInt32(damage),
"기본 대미지 1 (기획 04 §5-1, Phase 3-B 튠 전 파일럿 값)."); "기본 대미지 1 (기획 04 §5-1, Phase 3-B 튠 전 파일럿 값).");
} }
@ -59,10 +91,12 @@ public class PlayerAttackTests
public void AttackHitbox_Active_Duration_Is_Positive() public void AttackHitbox_Active_Duration_Is_Positive()
{ {
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(PlayerPrefabPath);
var hitbox = prefab.GetComponent<Platformer.Mechanics.AttackHitbox>(); var hitbox = FindComponentByFullName(prefab, AttackHitboxType);
Assert.IsNotNull(hitbox); Assert.IsNotNull(hitbox);
Assert.Greater(hitbox.activeDuration, 0f, var duration = GetFieldOrProperty(hitbox, "activeDuration");
Assert.IsNotNull(duration, "AttackHitbox.activeDuration 필드/프로퍼티 접근 불가");
Assert.Greater(System.Convert.ToSingle(duration), 0f,
"activeDuration 가 0 이하면 OverlapBox 판정이 즉시 종료되어 공격 무효."); "activeDuration 가 0 이하면 OverlapBox 판정이 즉시 종료되어 공격 무효.");
} }
@ -72,7 +106,7 @@ public class PlayerAttackTests
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath);
Assert.IsNotNull(prefab, $"Enemy.prefab not found at {EnemyPrefabPath}"); Assert.IsNotNull(prefab, $"Enemy.prefab not found at {EnemyPrefabPath}");
var health = prefab.GetComponent<Platformer.Mechanics.Health>(); var health = FindComponentByFullName(prefab, HealthType);
Assert.IsNotNull(health, Assert.IsNotNull(health,
"Enemy.prefab 에 Health 컴포넌트 누락. " + "Enemy.prefab 에 Health 컴포넌트 누락. " +
"BT5-Dev 2단계 재위임 집행분 (2026-04-23) 이 prefab YAML 에 반영되어야 함. " + "BT5-Dev 2단계 재위임 집행분 (2026-04-23) 이 prefab YAML 에 반영되어야 함. " +
@ -83,10 +117,12 @@ public class PlayerAttackTests
public void Enemy_Prefab_Health_MaxHP_Is_One() public void Enemy_Prefab_Health_MaxHP_Is_One()
{ {
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath);
var health = prefab.GetComponent<Platformer.Mechanics.Health>(); var health = FindComponentByFullName(prefab, HealthType);
Assert.IsNotNull(health); Assert.IsNotNull(health);
Assert.AreEqual(1, health.maxHP, var maxHP = GetFieldOrProperty(health, "maxHP");
Assert.IsNotNull(maxHP, "Health.maxHP 필드/프로퍼티 접근 불가");
Assert.AreEqual(1, System.Convert.ToInt32(maxHP),
"일반 적 기본 maxHP 1 (코어 룰 7 정합, 첫 세팅)."); "일반 적 기본 maxHP 1 (코어 룰 7 정합, 첫 세팅).");
} }
@ -94,7 +130,7 @@ public class PlayerAttackTests
public void Enemy_Prefab_Has_EnemyController_Component() public void Enemy_Prefab_Has_EnemyController_Component()
{ {
var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath); var prefab = AssetDatabase.LoadAssetAtPath<GameObject>(EnemyPrefabPath);
var controller = prefab.GetComponent<Platformer.Mechanics.EnemyController>(); var controller = FindComponentByFullName(prefab, EnemyControllerType);
Assert.IsNotNull(controller, Assert.IsNotNull(controller,
"EnemyDeath 체인에 EnemyController 필수 (AttackHitbox.Update 에서 Schedule<EnemyDeath>().enemy = enemy)."); "EnemyDeath 체인에 EnemyController 필수 (AttackHitbox.Update 에서 Schedule<EnemyDeath>().enemy = enemy).");
} }

View File

@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: d1dde48bf06739747a199e6d45b3fd18