콘텐츠로 이동

Unity Test Framework — 유닛 테스트와 플레이 모드 테스트

Unity Test Framework(UTF)는 NUnit 기반의 공식 테스트 솔루션입니다. EditMode 테스트(빠른 유닛 테스트)와 PlayMode 테스트(씬 실행 테스트) 두 가지 모드를 지원합니다.


Package Manager → Unity Registry → Test Framework → Install
Assets/
Tests/
EditMode/ ← EditMode 테스트 폴더
Tests.asmdef ← Assembly Definition (testPlatforms: Editor)
PlayMode/ ← PlayMode 테스트 폴더
Tests.asmdef ← Assembly Definition (testPlatforms: PlayMode)

2. EditMode 테스트 — 유닛 테스트

섹션 제목: “2. EditMode 테스트 — 유닛 테스트”
using NUnit.Framework;
public class DamageCalculatorTests
{
private DamageCalculator _calc;
[SetUp]
public void Setup()
{
_calc = new DamageCalculator();
}
[Test]
public void Calculate_WithCriticalHit_ReturnDoubledDamage()
{
float result = _calc.Calculate(baseDamage: 100f, isCritical: true);
Assert.AreEqual(200f, result, delta: 0.01f);
}
[Test]
[TestCase(100f, false, 100f)]
[TestCase(100f, true, 200f)]
[TestCase(0f, true, 0f)]
public void Calculate_VariousInputs(float base_, bool crit, float expected)
{
Assert.AreEqual(expected, _calc.Calculate(base_, crit), 0.01f);
}
}

3. PlayMode 테스트 — 코루틴 테스트

섹션 제목: “3. PlayMode 테스트 — 코루틴 테스트”
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public class PlayerMovementTests
{
private GameObject _playerObj;
[SetUp]
public void Setup()
{
_playerObj = new GameObject("Player");
_playerObj.AddComponent<PlayerMovement>();
_playerObj.AddComponent<CharacterController>();
}
[TearDown]
public void Teardown()
{
Object.Destroy(_playerObj);
}
[UnityTest]
public IEnumerator MoveForward_AfterOneSecond_MovesExpectedDistance()
{
var movement = _playerObj.GetComponent<PlayerMovement>();
movement.MoveInput = Vector2.up;
yield return new WaitForSeconds(1f);
float moved = _playerObj.transform.position.z;
Assert.Greater(moved, 4.5f, "1초 후 5m 이동해야 함");
}
}

using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
public class GameSceneTests
{
[UnitySetUp]
public IEnumerator LoadScene()
{
SceneManager.LoadScene("GameScene");
yield return null; // 씬 로드 대기
}
[UnityTest]
public IEnumerator EnemySpawner_SpawnsCorrectCount()
{
yield return new WaitForSeconds(2f);
var enemies = Object.FindObjectsByType<Enemy>(FindObjectsSortMode.None);
Assert.AreEqual(5, enemies.Length);
}
}

UTF는 NSubstitute를 공식 지원하지 않지만, 인터페이스를 이용한 수동 모킹으로 의존성을 분리할 수 있습니다.

// 인터페이스 정의
public interface IScoreService
{
int GetHighScore(string playerId);
void SaveScore(string playerId, int score);
}
// 테스트용 Fake
public class FakeScoreService : IScoreService
{
private readonly Dictionary<string, int> _scores = new();
public int GetHighScore(string id) => _scores.GetValueOrDefault(id, 0);
public void SaveScore(string id, int score) => _scores[id] = score;
}
// 테스트
[Test]
public void PlayerController_SavesHighScore_WhenScoreExceedsPrevious()
{
var scoreService = new FakeScoreService();
var controller = new PlayerController(scoreService);
controller.AddScore(500);
controller.EndGame("player1");
Assert.AreEqual(500, scoreService.GetHighScore("player1"));
}

using UnityEngine.TestTools;
[Test]
public void InvalidInput_LogsError()
{
LogAssert.Expect(LogType.Error, "Invalid input value");
_system.Process(-1); // 내부에서 Debug.LogError 호출 예상
}

Terminal window
# 커맨드라인 테스트 실행
Unity -runTests \
-projectPath /path/to/project \
-testPlatform EditMode \
-testResults results.xml \
-batchmode -nographics -quit

GitHub Actions 예시:

- name: Run Unity Tests
run: |
$UNITY_PATH -runTests -projectPath . \
-testPlatform EditMode \
-testResults results/results.xml \
-batchmode -nographics

EditMode 테스트로 게임 로직을 빠르게 검증하고, PlayMode 테스트로 씬 기반 통합 시나리오를 검증하세요. 인터페이스 기반 설계와 Fake 객체를 조합하면 Unity의 MonoBehaviour 의존성 없이도 핵심 로직을 테스트할 수 있습니다.