게임 개발에서 TDD를 도입하려면 먼저 무엇을 테스트할지부터 정해야 한다

테스트 주도 개발(TDD)은 구현보다 테스트를 먼저 작성하고, 그 테스트를 통과시키는 방향으로 코드를 발전시키는 개발 방식이다. 게임 개발에서 TDD를 이야기하면 대개 두 반응이 동시에 나온다. 하나는 “게임은 화면과 상호작용이 전부인데 뭘 테스트하나?”라는 회의론이고, 다른 하나는 “모든 시스템을 자동화해야 한다”는 과욕이다. 둘 다 현실과는 조금 다르다.
게임은 분명히 테스트하기 까다로운 소프트웨어다. 입력, 물리, 애니메이션, 네트워크, 렌더링이 강하게 얽혀 있고, 플레이 감각 같은 요소는 자동 테스트만으로 판정하기 어렵다. 그렇다고 해서 테스트가 소용없다는 뜻은 아니다. 오히려 이 복잡성 때문에, 어디까지를 자동화하고 어디부터는 사람의 플레이 검증으로 남길지를 더 명확히 나눠야 한다.
Unity 공식 문서를 보면 Unity Test Framework는 에디트 모드(Edit Mode)와 플레이 모드(Play Mode) 테스트를 모두 지원한다. 즉, 게임 개발에서도 테스트는 가능한 전제다. 다만 핵심은 “게임 전체를 TDD로 통제한다”가 아니라, 빠르게 망가질 수 있는 핵심 로직을 테스트 가능한 형태로 떼어내는 것에 있다.
게임에서 TDD가 어려운 이유
웹 서비스에서 테스트하기 좋은 대상은 보통 함수 입력과 출력이 비교적 분명하다. 반면 게임에서는 같은 요구사항도 여러 층위로 나뉜다. 예를 들어 “플레이어가 포션을 사용하면 체력이 회복된다”는 기능 하나만 해도 다음이 섞여 있다.
- 포션 사용 가능 여부 판단
- 수치 계산과 상태 갱신
- 인벤토리 차감
- UI 반영
- 사운드와 이펙트 재생
- 네트워크 동기화
이 중 어떤 것은 순수 로직이고, 어떤 것은 엔진과 장면 상태에 깊게 묶여 있다. 테스트를 어렵게 만드는 건 대개 뒤쪽이다. 그래서 TDD를 잘 도입한 팀은 “기능 단위”보다 “의존성 단위”로 먼저 자른다. 엔진 없이 검증 가능한 부분과, 실제 런타임이 필요한 부분을 나누는 것이다.
Unity 문서도 에디트 모드 테스트와 플레이 모드 테스트를 구분한다. 에디트 모드 테스트는 에디터에서 빠르게 실행되며 비교적 순수한 코드 검증에 적합하고, 플레이 모드 테스트는 런타임 시점의 게임 코드를 실행하는 데 적합하다. 이 구분만 이해해도 테스트 전략이 훨씬 현실적으로 변한다.
무엇을 먼저 TDD로 가져와야 하나
게임에서 TDD의 첫 대상은 대체로 플레이 감각이 아니라 규칙이다. 다음 같은 것들이 대표적이다.
- 전투 수식과 데미지 계산
- 인벤토리 추가/제거와 예외 처리
- 경험치와 레벨업 규칙
- 퀘스트 상태 전이
- 상점 구매/환불 로직
- 서버 검증 규칙
- 저장 데이터 직렬화 규칙
이런 코드는 입력과 출력이 비교적 분명하고, 한번 망가지면 플레이어 자산이나 밸런스에 직접 영향을 준다. 무엇보다 Unity 장면이나 GameObject를 띄우지 않고도 검증 가능한 경우가 많다.
반대로 처음부터 TDD로 끌고 오기 어려운 대상도 있다.
- 카메라 흔들림의 체감 품질
- 타격감과 애니메이션 타이밍의 만족도
- 레벨 동선의 재미
- 파티클 연출의 설득력
- 초보자 온보딩의 이해도
이런 영역은 테스트 코드보다 플레이 테스트, 사용자 관찰, 영상 리뷰가 더 강한 도구다. 자동화가 불가능한 것은 아니지만, 초기에 여기부터 붙잡으면 비용 대비 효과가 떨어진다.
Unity에서는 Edit Mode와 Play Mode를 다르게 써야 한다
Unity Test Framework 문서를 보면 에디트 모드 테스트는 에디터 안에서 빠르게 실행되고, 플레이 모드 테스트는 실제 게임 코드가 런타임처럼 동작하는 상황을 검증하는 데 쓰인다. 이 둘을 섞어 생각하면 테스트가 금방 무거워진다.
실무적으로는 이렇게 나누는 편이 좋다.
에디트 모드 테스트
빠르고 자주 돌려야 하는 테스트다.
- 순수 계산 로직
- 상태 전이
- 데이터 검증
- 저장 포맷 변환
- 규칙 기반 시스템
예를 들어 InventoryService, DamageCalculator, QuestStateMachine 같은 코드는 여기로 보내는 것이 좋다.
플레이 모드 테스트
런타임 시점의 결합부를 확인하는 테스트다.
- 씬에서 컴포넌트가 함께 동작하는지
- 코루틴이나 시간 경과가 들어가는지
- 애니메이션 이벤트나 충돌 트리거가 연결되는지
- 런타임 초기화 흐름이 정상인지
플레이 모드 테스트는 꼭 필요하지만, 수가 많아질수록 느려지고 유지비도 올라간다. 그래서 많은 팀이 에디트 모드 테스트를 기본 방어선으로 두고, 플레이 모드 테스트는 결합 위험이 큰 경계만 얇게 덮는다.
테스트하기 쉬운 구조는 대개 변경하기도 쉽다
Unity 테스트 프레임워크 문서는 테스트를 별도 어셈블리로 구성하고, 테스트 대상 코드가 있는 어셈블리를 참조하도록 권장한다. 이 말은 곧 테스트를 잘 하려면 런타임 로직도 어셈블리 단위로 분리해야 한다는 뜻이다.
이 구조는 TDD를 위한 편의에 그치지 않는다. 게임 코드 자체를 더 건강하게 만든다.
예를 들어 다음처럼 나누면 좋다.
MonoBehaviour는 입력 수집, 씬 연결, 프레젠테이션 담당- 도메인 서비스는 계산과 규칙 담당
- 저장소/네트워크는 인터페이스 뒤로 숨김
- 테스트는 도메인 서비스와 인터페이스 단위에서 먼저 작성
이렇게 하면 GameObject.Find, 씬 로드, 애니메이션 상태, 네트워크 연결 같은 무거운 의존성을 테스트에서 대부분 걷어낼 수 있다. 그리고 테스트가 쉬워진 만큼, 기획 변경이 왔을 때도 수정 범위를 좁게 유지하기가 쉬워진다.
TDD의 가장 큰 이익은 “테스트가 많다”가 아니라 “변경이 덜 무섭다”에 있다.
테스트 피라미드 관점이 게임에도 여전히 유효하다
Martin Fowler가 소개한 테스트 피라미드의 핵심은 단순하다. 빠르고 작은 테스트를 많이 두고, 느리고 큰 테스트는 적게 두라는 것이다. 이 원칙은 게임에서도 크게 다르지 않다.
게임 쪽으로 옮기면 보통 이런 형태가 된다.
- 가장 아래: 순수 로직 유닛 테스트
- 중간: 시스템 통합 테스트, 플레이 모드 테스트
- 가장 위: 사람 손이 들어가는 플레이 테스트, QA 시나리오, 빌드 검증
문제는 많은 팀이 반대로 간다는 데 있다. 사람 손으로만 플레이해 보거나, 반대로 모든 것을 엔드투엔드처럼 자동화하려고 한다. 전자는 회귀를 놓치기 쉽고, 후자는 유지비가 폭증한다.
그래서 게임에서의 좋은 테스트 전략은 “자동화 가능한 규칙을 아래층에 최대한 모으고, 체감 품질은 위층에서 사람 검증으로 다룬다”에 가깝다.
CI에 올릴 때는 ‘완벽한 자동화’보다 ‘깨졌다는 신호’가 중요하다
테스트는 로컬에서만 돌리면 반쪽짜리다. CI에 올라가야 팀 전체의 안전장치가 된다. GameCI 문서를 보면 GitHub Actions에서 game-ci/unity-test-runner를 사용해 Unity 테스트를 돌리고, 결과를 artifact로 업로드하는 흐름이 잘 정리돼 있다.
여기서 중요한 건 거대한 파이프라인을 만드는 것이 아니다.
- 커밋이나 PR마다 에디트 모드 테스트를 빠르게 돌린다
- 느린 플레이 모드 테스트는 범위를 제한한다
- 실패 결과는 팀이 바로 볼 수 있게 남긴다
이 정도만 되어도 테스트는 “있으면 좋은 것”에서 “머지 전에 한 번 더 생각하게 만드는 구조”로 바뀐다.
커버리지 숫자보다 더 중요한 것
게임 개발 글에서 자주 보이는 함정이 하나 있다. 높은 커버리지 수치 자체를 목표처럼 내세우는 것이다. 숫자는 참고 지표일 뿐, 그 자체가 품질을 보장하지는 않는다.
게임에서 더 중요한 질문은 보통 이쪽이다.
- 경제와 보상 규칙이 깨졌을 때 바로 잡을 수 있는가
- 저장 데이터 회귀를 빠르게 발견할 수 있는가
- 기획 변경이 와도 핵심 시스템을 자신 있게 고칠 수 있는가
- 엔진 결합부와 순수 로직이 구분되어 있는가
이 질문에 “그렇다”고 답할 수 있다면, 커버리지 숫자가 다소 낮아도 테스트는 제 역할을 하고 있을 가능성이 높다.
핵심 정리
게임에서 TDD는 모든 것을 테스트하자는 운동이 아니다. Unity의 에디트 모드·플레이 모드 테스트 구분을 이해하고, 먼저 검증해야 할 핵심 규칙을 엔진 의존성에서 분리하는 작업에 가깝다. 전투 수식, 인벤토리, 퀘스트 상태, 저장 포맷 같은 로직은 TDD의 좋은 첫 대상이고, 타격감이나 연출 품질 같은 영역은 플레이 테스트가 더 적합하다.
테스트를 잘 도입한 게임 팀은 대개 테스트 코드만 잘 쓰는 팀이 아니라, 변경 가능한 구조를 만든 팀이다. TDD의 목적은 커버리지 자랑이 아니라, 기획이 바뀌어도 시스템을 덜 무섭게 고칠 수 있게 만드는 데 있다.
마치며
게임은 본질적으로 상호작용과 체감의 매체라서, 모든 것을 테스트 코드로 환원할 수는 없다. 하지만 그렇기 때문에 더더욱 규칙과 상태 전이를 자동으로 지켜야 한다. 사람이 잡아야 할 문제와, 코드가 대신 잡아줄 수 있는 문제를 나누는 순간부터 TDD는 게임에서도 현실적인 도구가 된다.
처음부터 거대한 테스트 체계를 만들 필요는 없다. 인벤토리 하나, 상점 하나, 퀘스트 상태머신 하나부터 테스트 가능한 구조로 떼어내면 된다. 그 작은 분리가 나중에는 프로젝트 전체의 변경 비용을 바꾼다.
참고 자료
- Unity Manual, Test Framework: https://docs.unity3d.com/kr/6000.0/Manual/com.unity.test-framework.html
- Unity Test Framework, Edit Mode vs Play Mode tests: https://docs.unity3d.com/kr/Packages/com.unity.test-framework%402.0/manual/edit-mode-vs-play-mode-tests.html
- Unity Manual, Unity Test Runner: https://docs.unity3d.com/cn/2018.3/Manual/PlaymodeTestFramework.html
- GameCI, Unity Test Runner for GitHub Actions: https://game.ci/docs/github/test-runner/
- Martin Fowler, The Practical Test Pyramid: https://martinfowler.com/articles/practical-test-pyramid.html
- Kent Beck. (2002). Test-Driven Development: By Example. Addison-Wesley.