[Unity] using NUnit.Framework;

KYO
KYO
∙∙조회 19
[Unity] using NUnit.Framework;

EditMode Test Report

  • 에디터에서 바로 실행 가능한 테스트

PlayMode Test Report

  • 플레이 모드 진입해야되는 테스트

들어가며

복선이 있긴 했다

나는 System.Collection의 리스트를 쓰고 싶었는데, 자동완성이 NUnit.Framework의 List를 찾아서 원하지 않은 using문이 생겼던 기억이 자주 있다. 당연히 이 네임스페이스에 대해서는 전혀 아는 바가 없었다.

"검증기를 만들어서 시연할 것"

곧 기업협약 프로젝트가 시작된다. 우리 팀에 주어진 주요 과제를 해결하기 위해서는, 게임 메카닉이 결정론적으로 동작하는지 검증하는 프로그램을 만들어야 했다. 이를 위한 검증기가 필요해졌고, 그렇게 NUnit.Framework를 다시 만났다.

NUnit.Framework 는 무엇인가?

개요

이 네임스페이스는 단위 테스트를 도와주는 순수 C#의 네임스페이스다.

단위 테스트란, 주로 메서드 하나 단위의 작은 코드 단위가 의도대로 동작하는지 확인하는 것을 의미한다.

???: 그냥 PlayMode 진입해서 눈으로 확인하면 안 돼요?

안됩니다.

장점

  • 빠름: 빌드 배치 툴을 만드는 방법을 배웠듯이, 같은 방법으로 테스트용 배치툴을 만들어 여러 테스트 프로세스를 병렬로 돌릴 수 있다.
  • 반복 가능: 동일한 조건에서 동일한 결과를 낸다. 이 부분에서는 결정론적이다.
  • 회귀 예방: 형상관리로 협업하다보면 종종 '어? 왜 예전 버전으로 돌아갔지?' 가 생기는데, 이를 예방한다.

Unity Test Framework(UTF)

Unity는 자체적인 테스트 도구가 있으나, 이는 NUnit을 Unity에 맞게 커스터마이즈하여 제공되는 것이다. 즉, 우리가 Unity에서 쓰는 NUnit이 UTF다.

주요 어트리뷰트

어트리뷰트

역할

[Test]

메서드에 붙는다. 이 메서드는 테스트용이다.

[TestFixture]

유니티에서는 생략 가능하다. 이 클래스가 테스트 메서드를 포함함을 의미한다.

[SetUp]

각 테스트 실행 직전에 호출된다.

[TearDown]

각 테스트 실행 직후에 호출된다.

[TestCase(...)]

하나의 테스트를 여러 입력값으로 반복 실행한다.

[UnityTest]

UTF 전용. 코루틴처럼 yield return으로 프레임을 넘기며 테스트한다.

주요 Assert API

Assert.*

역할

AreEqual(expected, actual);

AreEqual(expected, actual, delta);

두 값이 같은가?

또는 두 값이 delta의 허용 오차를 두고 같은가?(부동소수점)

IsTrue(condition);

참인가?

IsFalse(condition);

거짓인가?

IsNull(obj);

null인가?

Throws<ArgumentException>(Action action);

action은 예외가 나야 정상인 코드.

사용법

1. 어셈블리(asmdef) 만들기

어셈블리는 프로젝트 폴더 내에 .asmdef 파일로 만들 수 있고, 이 파일은 해당 모듈(네임스페이스)이 참조할 수 있는 모듈이나 규칙(unsafe 허용 등)을 정의한다. UTF는 NUnit을 참조하는 어셈블리 내에서만 작동하므로, 이 파일을 생성해야한다. 당연히 테스트를 위해 참조해야할 게임 메카닉이 정의된 모듈도 참조를 명시할 필요가 있다.

2. Test Runner 창 열기

Window > General > Test Runner 에서 위의 어셈블리 파일을 자동 생성 할 수 있다.

  • EditMode 테스트: 에디터에서 즉시 실행된다. 순수 C# 로직 검증에 적합하다.
  • PlayMode 테스트: 실제 플레이 루프 속에서 실행된다. MonoBehaviour, 프레임 진행, 물리 등 엔진이 관여해야할 때 사용한다.

사실 진짜 체감되는 장점은 따로 있음

나 같은 신입도 데이터 모델과 POCO 로직, MonoBehaviour 사이클 등을 분리할 수 있도록 도와주는 기준이 된다.

만약 기능 구현은 다 됐는데 단위 테스트가 가능한 형태가 아니라면, 뭔가 냄새가 날 것이다.

주의해야할 점

각 테스트가 독립되어있으면서도 결정론적이어야한다.

이 명제가 훼손되면 테스트 자체가 성립할 수 없다.

  1. 따라서 static 필드나 싱글턴에의 의존은 실행 순서에 따라 결과가 바뀔 수 있어 조심해야한다.
  2. 의도한 에러라면 Debug.LogError 쓰지 말고 LogAssert.Expert 쓰자. LogError는 테스트가 실패처리된다.
  3. float 비교에는 허용 오차를 포함한다
  4. 테스트를 잘게 쪼갠다. Assert가 많아지면 테스트 실패시 원인 파악이 귀찮다.