자동화 QA 봇으로 던전 충돌과 이동 연산을 검증하는 방법
자동화 QA 봇으로 절차적 던전의 벽 충돌, 이동 벡터, 프레임 의존성, 경로 이탈을 반복 검증하는 방법을 정리합니다. 재현 가능한 시드와 불변 조건으로 빌드 전 회귀 버그를 빠르게 찾습니다.

핵심 요약
자동화 QA 봇은 같은 던전 시드와 입력 시퀀스를 반복 실행해 플레이어가 벽을 통과하거나 이동 거리가 프레임률에 따라 달라지는 문제를 빠르게 찾는다. 핵심은 충돌 결과를 눈으로 판정하지 않고 최종 위치, 침투 깊이, 이동 거리, 도달 가능 여부를 불변 조건으로 정의해 실패 로그와 함께 저장하는 것이다.
이 글의 예시는 Unity 6 LTS, C#, PhysX 기반의 3D 캐릭터 이동을 가정한다. 엔진이 달라도 테스트 설계 원칙은 동일하다.
던전 충돌 및 이동 연산 버그는 왜 반복해서 발생하는가?
던전 이동 버그는 개별 기능보다 경계 조건에서 자주 발생한다. 절차적으로 생성한 좁은 복도, 대각선 입력, 경사면, 낮은 프레임률, 이동 중 문이 닫히는 상황이 한 번에 겹치면 수동 테스트만으로는 조합을 충분히 확인하기 어렵다.
대표적인 원인은 다음과 같다.
| 원인 | 증상 | 자동화 판정 기준 |
|---|---|---|
deltaTime 누락 또는 중복 적용 | 30 FPS와 120 FPS의 이동 거리가 다름 | 동일한 시뮬레이션 시간의 위치 오차가 허용 범위 이내 |
| 대각선 벡터 미정규화 | 대각선 이동이 직선보다 빠름 | 입력 크기 1 기준 최대 속도를 넘지 않음 |
| Collider 간 초기 겹침 | 스폰 직후 벽에 끼거나 튕김 | 침투 깊이와 복구 후 위치가 임계값 이내 |
| 고속 이동의 터널링 | 얇은 벽이나 문을 통과함 | 이동 전후 선분에 차단 Collider가 있으면 반대편 도달 금지 |
| 절차 생성 오차 | 방과 복도가 연결되지 않음 | 시작 지점에서 목표 지점까지 경로 존재 |
일반적인 이동 적분은 다음처럼 표현할 수 있다.
여기서 p는 위치, d는 입력 방향, v는 이동 속도다. d를 정규화하지 않으면 (1, 0, 1)의 길이가 1보다 커져 대각선 이동 속도가 증가한다.

자동화 QA 봇은 무엇을 기록해야 하는가?
봇은 단순히 이동 명령을 보내는 도구가 아니라 판정 가능한 관측 데이터를 만드는 테스트 실행자다. 최소한 아래 값을 테스트 결과에 남긴다.
seed: 던전 생성 시드buildVersion,sceneName,physicsFixedDeltaTime- 프레임 또는 FixedUpdate 틱 번호
- 입력 벡터와 예상 이동 거리
- 이동 전후 위치 및 실제 이동 거리
- 충돌한 Collider 이름, 레이어, 접촉 법선
Physics.ComputePenetration으로 계산한 침투 깊이- 실패 시 스크린샷, 재현용 입력 시퀀스 JSON
이동 거리와 벽 관통을 어떻게 판정할까?
테스트마다 허용 오차를 명시한다. 예를 들어 속도 4.0 m/s, 시뮬레이션 시간 2.0초라면 장애물이 없는 직선 구간의 기대 거리는 다음과 같다.
부동소수점과 물리 시뮬레이션 오차를 고려해 0.02 m 이내를 통과 기준으로 둘 수 있다. 다만 이 값은 보편적인 정답이 아니다. 캐릭터 컨트롤러 반지름, Fixed Timestep, 이동 구현 방식에 맞춰 프로젝트별 기준값으로 관리해야 한다.
벽 관통은 최종 위치만으로 판정하면 놓칠 수 있다. 이동 전 위치에서 이동 후 위치까지 CapsuleCast를 수행하고 차단 레이어의 충돌체가 감지됐는데도 봇이 반대편 영역에 도달했다면 실패로 처리한다.
자동화 QA 봇 테스트는 어떻게 구현할까?
1. 재현 가능한 던전과 입력을 고정한다
난수 시드와 봇 입력 시퀀스를 고정해야 실패를 다시 만들 수 있다. 생성기 내부에서 시간값이나 전역 난수를 섞으면 같은 seed를 넣어도 레이아웃이 달라질 수 있으므로 던전 생성 전용 System.Random 인스턴스를 사용한다.
public sealed class DungeonTestCase
{
public int Seed { get; }
public Vector2[] Inputs { get; }
public float StepSeconds { get; }
public DungeonTestCase(int seed, Vector2[] inputs, float stepSeconds = 0.02f)
{
Seed = seed;
Inputs = inputs;
StepSeconds = stepSeconds;
}
}
이 생성자 형태는 Unity 6의 C# 컴파일러에서 지원되지 않는 C# 11 required와 init 접근자를 사용하지 않습니다.
입력은 사람이 누른 키가 아니라 정규화 전의 Vector2 값으로 기록한다. 이렇게 하면 직선, 대각선, 급격한 방향 전환, 벽을 향한 지속 이동을 정확히 재실행할 수 있다.
2. 봇이 고정 틱으로 이동하고 상태를 수집하게 한다
물리 기반 이동은 Update()가 아니라 FixedUpdate()와 같은 고정 시뮬레이션 경로에서 테스트하는 편이 안정적이다. 아래 예시는 Rigidbody.MovePosition을 사용하는 이동 컴포넌트의 핵심 형태다.
public void SimulateMove(Vector2 rawInput, float fixedDeltaTime)
{
Vector3 direction = new Vector3(rawInput.x, 0f, rawInput.y);
if (direction.sqrMagnitude > 1f)
{
direction.Normalize();
}
Vector3 delta = direction * moveSpeed * fixedDeltaTime;
body.MovePosition(body.position + delta);
}
테스트 러너는 각 틱마다 이동 전후 위치와 충돌 정보를 수집한다. 벽 통과를 찾는 경우에는 캐릭터 콜라이더와 같은 반지름·높이를 사용한 CapsuleCast를 먼저 수행해 이동 경로의 차단 여부도 함께 확인한다.
bool IsBlocked(Vector3 start, Vector3 delta, float radius, float height, LayerMask wallMask)
{
Vector3 bottom = start + Vector3.up * radius;
Vector3 top = start + Vector3.up * (height - radius);
return Physics.CapsuleCast(
bottom,
top,
radius,
delta.normalized,
out _,
delta.magnitude,
wallMask,
QueryTriggerInteraction.Ignore);
}
3. 불변 조건으로 통과와 실패를 선언한다
좋은 자동화 테스트는 “자연스럽게 움직였다”가 아니라 검증 가능한 조건을 사용한다. 다음은 던전 이동에 적합한 불변 조건 예시다.
- 빈 복도에서 실제 이동 거리는 기대 거리의 오차 범위 안에 있어야 한다.
- 차단 벽이 있는 방향으로 이동한 뒤 캐릭터 중심은 벽 반대편 영역에 있으면 안 된다.
- 모든 생성 시드에서 시작 방과 출구 사이에 그래프 경로가 하나 이상 존재해야 한다.
- 테스트 종료 시 플레이어 Collider의 침투 깊이는
0.001 m이하이어야 한다. - 같은 빌드·플랫폼·물리 설정에서 같은 시드와 입력은 같은 맵 해시와 판정 결과를 재현해야 한다.
flowchart TD
A[시드와 입력 시퀀스 로드] --> B[던전 생성]
B --> C[고정 틱으로 봇 이동]
C --> D[위치·충돌·침투 기록]
D --> E{불변 조건 충족?}
E -->|예| F[결과와 메트릭 저장]
E -->|아니오| G[시드·입력·스크린샷 저장]
G --> H[회귀 테스트 목록에 등록]
실패한 시드와 입력은 일회성 로그로 끝내지 말고 회귀 테스트 케이스로 승격한다. 수정 후 같은 케이스가 통과하는지 확인해야 충돌 해결 과정에서 다른 구간을 깨뜨리지 않는다.
던전 생성 테스트와 플레이 이동 테스트를 분리해야 하는 이유
둘을 한 테스트에 모두 넣으면 실패 원인을 찾기 어렵다. 맵이 연결되지 않은 문제와 캐릭터가 벽을 통과한 문제는 관측값과 담당 코드가 다르다.
| 테스트 계층 | 검증 대상 | 대표 지표 |
|---|---|---|
| 생성 단위 테스트 | 방 배치, 복도 연결, 타일 점유 | 연결 요소 수, 출구 도달 가능 여부 |
| 물리 통합 테스트 | Collider 배치, 이동 차단, 경사 처리 | Cast 충돌, 침투 깊이, 접촉 법선 |
| 봇 시나리오 테스트 | 실제 입력 흐름과 상호작용 | 완료율, 막힘 횟수, 목표 도달 시간 |
| 빌드 회귀 테스트 | 플랫폼별 실행 결과 | 실패 시드 수, 크래시, 성능 예산 초과 |
생성 계층에서는 타일 그래프의 BFS 또는 A*로 연결성을 먼저 검증한다. 그 다음 물리 통합 테스트에서 실제 Collider가 그래프의 의도와 같게 배치됐는지 확인한다. 마지막으로 봇이 플레이어와 같은 이동 API를 사용해 목표까지 도달하는지 검증하면 원인을 좁히기 쉬워진다.

빌드 파이프라인에 자동화 QA 봇을 연결하는 방법
테스트 실행 기준을 작게 시작한다
처음부터 수천 개 시드를 모든 커밋에서 돌리면 피드백 시간이 길어진다. 다음처럼 단계별로 나누는 편이 실용적이다.
- 풀 리퀘스트마다 대표 시드 20개와 핵심 이동 시나리오를 실행한다.
- 야간 빌드에서 시드 1,000개 이상을 분산 실행하고 실패 케이스를 보관한다.
- 릴리스 후보에서는 이전에 발생한 모든 회귀 시드와 플랫폼별 이동 테스트를 실행한다.
실패 결과는 사람이 바로 재현할 수 있게 만든다
CI 로그에 seed=18473, inputCase=DiagonalWallSlide_03, tick=217처럼 복사 가능한 식별자를 남긴다. 가능하다면 테스트 장면에서 시드와 입력 파일을 인자로 받아 즉시 실행하는 개발용 명령도 제공한다.
DungeonTestRunner --seed 18473 --case DiagonalWallSlide_03 --fixed-delta 0.02
스크린샷만 저장하기보다 실패 직전 수십 틱의 위치·입력·충돌 데이터를 JSON으로 저장하는 것이 중요하다. 스크린샷은 결과를 보여 주지만 입력 기록은 원인을 재현하게 해 준다.
자주 묻는 질문 (FAQ)
봇이 목적지에 도달하면 충돌 테스트도 충분한가?
아니다. 목적지 도달은 큰 막힘을 찾는 지표일 뿐이다. 벽 관통, 미세한 끼임, 대각선 속도 증가처럼 플레이 경험을 해치는 문제는 이동 경로와 침투 깊이 같은 별도 조건으로 확인해야 한다.
모든 랜덤 시드를 매 커밋마다 검사해야 하는가?
그럴 필요는 없다. 커밋 단계에서는 대표 시드와 기존 실패 시드를 빠르게 실행하고 대규모 시드 탐색은 야간 빌드나 릴리스 후보 단계로 분리하는 것이 효율적이다.
물리 테스트에서 허용 오차를 0으로 두면 더 정확하지 않은가?
아니다. 부동소수점 계산과 물리 엔진의 접촉 보정 때문에 0 오차는 정상 동작도 실패로 만들 수 있다. 측정값을 모은 뒤 캐릭터 크기와 이동 거리 대비 의미 있는 오차 범위를 정해야 한다.
정리
자동화 QA 봇의 목적은 사람을 대신해 무작정 많이 걷는 것이 아니다. 재현 가능한 시드, 고정된 입력, 명확한 불변 조건, 실패 아티팩트를 결합해 던전 충돌과 이동 연산의 회귀를 빠르게 발견하는 데 있다. 먼저 대표 시드와 벽 충돌 시나리오부터 CI에 넣고 실패한 케이스를 지속적으로 회귀 목록에 추가하면 테스트 신뢰도가 꾸준히 높아진다.



