AI 보조 게임 개발

AI 보조 도구로 게임 시스템 단위 테스트 자동 생성하기

kr-gamedev 2026. 6. 5. 10:46

게임 개발에서 테스트는 늘 뒷전으로 밀린다. 마감은 코앞이고, 인벤토리 정렬 로직이나 데미지 계산식 같은 건 "한번 돌려보고 맞으면 됐다"로 넘어가기 일쑤다. 그런데 정작 출시 직전에 터지는 버그의 상당수가 이런 곳에서 나온다. 스택 가능한 아이템이 99개 넘어가면 음수가 되거나, 크리티컬 배율이 0이 되는 식의 경계 케이스 말이다.

문제는 단위 테스트를 손으로 짜는 게 지루하다는 데 있다. 함수 하나에 정상 케이스, 경계값, 예외 케이스까지 꼼꼼히 깔려면 테스트 코드가 본체 코드보다 길어진다. 이 반복적이고 정형화된 작업이야말로 AI 보조 도구가 가장 잘하는 영역이다. 로직은 사람이 설계하고, 그 로직을 검증하는 그물망은 AI에게 짜게 시키는 분업이 가능하다.

이번 글에서는 Unity 환경에서 게임 시스템 코드의 단위 테스트를 AI로 생성하고, 그 결과를 신뢰할 수 있게 다듬는 실전 흐름을 정리한다. 핵심은 "AI에게 통째로 맡기기"가 아니라 "AI가 빈 칸을 메우게 하고 사람이 경계를 정의하기"다.

1. 테스트하기 좋은 코드부터 만든다

AI가 좋은 테스트를 생성하려면, 대상 코드가 테스트하기 쉬운 모양이어야 한다. MonoBehaviour에 모든 로직이 박혀 있으면 AI도 사람도 손대기 어렵다. 순수 로직을 평범한 C# 클래스로 분리하는 것이 첫 단계다.

// 테스트 불가능한 형태: MonoBehaviour에 로직이 붙어 있음
public class PlayerHealth : MonoBehaviour
{
    [SerializeField] private int maxHp = 100;
    private int currentHp;

    public void TakeDamage(int raw, float armor)
    {
        currentHp -= Mathf.Max(1, (int)(raw * (1f - armor)));
        if (currentHp < 0) currentHp = 0;
    }
}

이걸 순수 로직과 컴포넌트로 쪼갠다. 데미지 계산은 정적 메서드 하나로 빼낸다.

// 순수 함수: 씬도 GameObject도 필요 없이 검증 가능
public static class DamageRules
{
    public static int Resolve(int raw, float armor)
    {
        float reduced = raw * (1f - Mathf.Clamp01(armor));
        return Mathf.Max(1, Mathf.RoundToInt(reduced));
    }
}

이렇게 분리해 두면 AI에게 "이 함수의 단위 테스트를 NUnit으로 짜줘"라고 했을 때 씬 셋업 같은 군더더기 없이 입력과 출력만 검증하는 깔끔한 테스트가 나온다.

2. AI에게 경계 케이스를 명시적으로 요구한다

AI 보조 도구에 단순히 "테스트 짜줘"라고만 하면 정상 케이스 두세 개만 만들어 준다. 진짜 가치는 경계와 예외에 있으므로, 프롬프트에 그 의도를 박아야 한다. 예를 들면 이렇게 요청한다.

"DamageRules.Resolve의 NUnit 테스트를 작성해줘. armor가 0, 1, 음수, 1 초과인 경우, raw가 0이거나 매우 클 때, 그리고 최소 데미지 1이 항상 보장되는지를 각각 다른 TestCase로 검증해줘."

그 결과로 나오는 테스트는 대략 다음 형태가 된다. C# 12의 표현을 그대로 살려도 좋다.

using NUnit.Framework;

[TestFixture]
public class DamageRulesTests
{
    [TestCase(100, 0f, 100)]   // 방어 없음
    [TestCase(100, 0.5f, 50)]  // 절반 경감
    [TestCase(100, 1f, 1)]     // 완전 방어여도 최소 1
    [TestCase(100, 2f, 1)]     // armor 초과는 1로 clamp
    [TestCase(100, -1f, 100)]  // 음수 armor는 0으로 clamp
    [TestCase(0, 0f, 1)]       // raw 0이어도 최소 1
    public void Resolve_ReturnsExpected(int raw, float armor, int expected)
    {
        Assert.That(DamageRules.Resolve(raw, armor), Is.EqualTo(expected));
    }
}

여기서 사람이 할 일은 expected 값이 진짜 우리 게임의 의도와 맞는지 검수하는 것이다. "완전 방어여도 1은 들어간다"가 디자인 의도인지, 아니면 0이어야 하는지는 AI가 알 수 없다. 이 판단이 곧 게임 디자인의 일부다.

3. 골든 테스트로 복잡한 시스템을 묶는다

데미지 한 줄짜리 함수는 쉽다. 문제는 인벤토리 병합, 스킬 트리 적용, 세이브 직렬화처럼 출력이 복잡한 시스템이다. 이럴 때는 골든 테스트(golden test) 방식이 유용하다. 현재 동작이 옳다고 판단되는 시점의 출력을 스냅샷으로 박아두고, 이후 코드를 바꿨을 때 출력이 달라지면 경고가 뜨게 하는 것이다.

AI에게 시킬 일은 "이 시스템의 대표 입력 시나리오 10개를 만들고, 각각의 출력을 JSON으로 직렬화해서 비교하는 테스트를 짜줘"다. 입력 시나리오를 다양하게 뽑아내는 건 AI가 사람보다 부지런하다.

[Test]
public void Inventory_MergeStacks_MatchesGolden()
{
    var inv = new Inventory();
    inv.Add("potion", 60);
    inv.Add("potion", 60);   // 99 스택 한도 초과 분기 검증
    inv.Add("sword", 1);

    string actual = JsonUtility.ToJson(inv.Snapshot());
    string golden = TestData.Load("inventory_merge_golden.json");

    Assert.That(actual, Is.EqualTo(golden));
}

골든 파일을 처음 만들 때는 출력이 맞는지 사람이 한번 검수한 뒤 커밋한다. 이후로는 의도치 않은 동작 변화가 즉시 빨간불로 잡힌다. 리팩토링할 때 특히 든든한 안전망이 된다.

4. 생성된 테스트는 반드시 사람이 통과시켜본다

AI가 만든 테스트를 그대로 믿으면 안 되는 이유가 하나 있다. AI는 종종 현재 코드의 버그까지 정답으로 간주해 테스트를 짠다. 즉 잘못된 동작을 "맞다"고 박아버리는 것이다. 그래서 생성 직후 반드시 Test Runner로 돌려보고, 통과한 케이스 중에 의심스러운 expected가 없는지 눈으로 훑어야 한다.

추천하는 흐름은 이렇다. 먼저 AI에게 테스트를 생성시킨다. 그다음 Unity Test Runner에서 전부 돌린다. 통과한 것 중 경계 케이스의 기대값을 사람이 검수한다. 마지막으로 일부러 본체 코드에 버그를 심어보고 테스트가 빨간불로 바뀌는지 확인한다. 이 마지막 단계가 테스트의 실효성을 보장하는 핵심이다. 빨간불이 안 뜨는 테스트는 그물망에 구멍이 뚫린 것이다.

결론

AI 보조 도구는 단위 테스트라는 지루한 작업의 부담을 크게 덜어준다. 정리하면 네 가지다. 첫째, 순수 로직을 MonoBehaviour에서 분리해 테스트하기 좋은 형태로 만든다. 둘째, 프롬프트에 경계와 예외 케이스를 명시적으로 요구해야 가치 있는 테스트가 나온다. 셋째, 출력이 복잡한 시스템은 골든 테스트로 스냅샷을 박아 변화 감지망을 친다. 넷째, 생성된 테스트는 반드시 사람이 돌려보고 일부러 버그를 심어 검증한다.

핵심 분업은 명확하다. 무엇이 옳은 동작인지 정의하는 것은 사람의 몫이고, 그 정의를 빠짐없이 코드로 옮기는 반복 노동은 AI의 몫이다. 이 선을 지키면 테스트 커버리지를 올리면서도 잘못된 안심에 빠지지 않을 수 있다.