Unity Test Runner

4 minute read

raywenderlich.comIntroduction To Unity Unit Testing을 정리하였습니다.

예제 게임 코드를 다운받으려면 위의 사이트에 가입해야 합니다.

유닛 테스트(Unit Test)란 무엇인가

유닛 테스트란 코드 내의 작은 기능 하나에 대해 테스트하는 함수입니다.

유닛 테스트를 작성할 때는

  • 하나의 테스트는 한 가지 기능만 테스트하도록 작성합니다.
  • 특정 부분 코드가 특정 시나리오에서 기대한 대로 동작하는 지 검증할 수 있도록 테스트를 설계합니다.

유닛 테스트 예제

사용자로부터 이름을 입력받는 함수에 대한 유닛 테스트를 만들어 봅니다.

public string name = ""
public void UpdateNameWithCharacter(char: character)
{
    // 1
    if (!Char.IsLetter(char))
    {
        return;
    }

    // 2
    if (name.Length > 10)
    {
        return;
    }

    // 3
    name += character;
}

어떤 동작이 테스트되어야 하는지 생각해서 유닛 테스트 함수들을 작성합니다. 유닛 테스트 함수명은 테스트 내용이 다 포함되도록 길게 짓는 것이 좋습니다.

  • Unit Test 1: 10글자 이상이면 name에 추가되지 않는다. UpdateNameDoesntAllowCharacterAddingToNameIfNameIsTenOrMoreCharactersInLength()
  • Unit Test 2: 입력된 글자가 name에 추가된다. UpdateNameAllowsLettersToBeAddedToName()
  • Unit Test 3: 글자가 아니면 name에 추가되지 않는다. UpdateNameDoesntAllowNonLettersToBeAddedToName()

유닛 테스트

유닛 테스트들을 그룹으로 묶어서 Test Suite를 만듭니다. 유닛 테스트 중 하나라도 실패하면 전체 테스트가 실패하게 됩니다.

예제 게임

1 2 3

Unity Test Runner 시작하기

유니티에서 Windows ▸General ▸Test Runner를 실행합니다.

4

1

NUnit과 테스트 폴더 설정

유니티에서 제공하는 유닛 테스트 기능은 C#에서 사용되는 NUnit 유닛 테스트 프레임워크를 사용하고 있습니다.

유닛 테스트를 위한 Tests 폴더를 만듭니다.

  1. Test Runner Window에서 PlayMode를 선택합니다.
  2. ProjectsAssets에서 테스트 폴더를 생성하고자 하는 위치를 클릭합니다.
  3. CreatePlayMode Test Assembly Folder 버튼을 클릭하면 Tests 폴더가 생성됩니다.

플레이모드 테스트 폴더 생성

PlayMode는 게임 플레이를 테스트할 때 사용되고, EditMode는 주로 Custom Inspector를 테스트할 때 사용됩니다.

Test Suite

유닛 테스트를 함수로 작성하므로, 이러한 유닛 테스트들을 담을 클래스가 필요합니다. Test Runner는 모든 테스트 클래스에 있는 유닛 테스트들을 실행합니다. 테스트하고자 하는 함수별로 묶거나, 기능 단위로 묶은 Test Suite로 테스트 클래스를 만듭니다.

Test Assembly와 Test Suite 설정

Tests 폴더를 선택하고 Test Runner 윈도우에서 Create Test Script in current folder를 누릅니다. 추가된 파일을 TestSuite라는 이름으로 만듭니다. 테스트 파일 추가

Tests 폴더에 보면 Tests.asmdef라는 어셈블리 정의 파일이 같이 생성되어 있습니다. 유니티에서 테스트 파일이나 테스트 코드가 제대로 나오지 않을 경우 테스트 어셈블리 정의 파일이 있는지 확인합니다. 테스트 코드가 게임 프로젝트에 있는 코드를 액세스하려면 게임 프로젝트의 어셈블리 정의 파일을 만들어서 테스트 어셈블리에 연결해야 합니다. Scripts 폴더에서 오른쪽 버튼을 클릭한 뒤 Create▸Assembly Definition을 실행합니다.

어셈블리 정의 클릭

GameAssembly라는 이름의 어셈블리 정의 파일을 만듭니다.

게임 어셈블리 정의 생성

Tests폴더의 Tests 어셈블리 정의 파일을 클릭합니다. InspectorAssembly Definition References에서 +버튼을 누릅니다.

어셈블리 참조 추가

게임 어셈블리 선택

추가한 어셈블리 참조 적용

첫번째 Unit Test 작성

운석이 아래로 떨어지는지 테스트하는 코드를 작성해 보겠습니다. TestSuite 파일을 열어서 코드를 다음과 같이 수정합니다.

using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using System.Collections;

public class TestSuite
{
	private Game game;

	// 1
	[UnityTest]
	public IEnumerator AsteroidsMoveDown()
	{
	    // 2
	    GameObject gameGameObject = 
	        MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
	    game = gameGameObject.GetComponent<Game>();
	    // 3
	    GameObject asteroid = game.GetSpawner().SpawnAsteroid();
	    // 4
	    float initialYPos = asteroid.transform.position.y;
	    // 5
	    yield return new WaitForSeconds(0.1f);
	    // 6
	    Assert.Less(asteroid.transform.position.y, initialYPos);
	    // 7
	    Object.Destroy(game.gameObject);
	}
}
  1. [UnityTest] Attribute은 유니티에게 이 함수가 유닛 테스트 코드라는 것을 알려줍니다. Test Runner 윈도우에 이 테스트 함수가 표시됩니다.
  2. SpawnAsteroid()로 운석을 하나 만들고 처음 위치를 기록합니다.
  3. UnityTest 함수는 코루틴이므로 yield return이 있어야 합니다. 운석이 떨어지려면 시간이 흘러야 하므로 0.1초를 기다립니다. 기다릴 필요가 없으면 null을 리턴하면 됩니다.
  4. Assert를 이용해서 운석의 위치가 처음 위치보다 아래인지 확인합니다. NUnit은 다양한 assertion 함수들을 제공합니다. 이 assertion으로 테스트 통과 여부가 결정됩니다.
  5. 생성한 게임 오브젝트를 꼭 삭제해야 합니다.

테스트 실행하기

Test Runner 윈도우에 작성한 AsteroidsMoveDown이 나타납니다. 10

Run All 버튼을 누르면 임시 Scene이 생성되어 테스트가 실행됩니다. 11

테스트에 통과하면 녹색 체크로 표시됩니다. 12

통합 테스트

통합 테스트

Test Suite에 다른 테스트 추가

우주선이 운석과 충돌하면 게임이 끝나는지 테스트합니다. TestSuite 파일에 다음 함수를 추가합니다.

[UnityTest]
public IEnumerator GameOverOccursOnAsteroidCollision()
{
    GameObject gameGameObject = 
       MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
    Game game = gameGameObject.GetComponent<Game>();
    GameObject asteroid = game.GetSpawner().SpawnAsteroid();
    //1
    asteroid.transform.position = game.GetShip().transform.position;
    //2
    yield return new WaitForSeconds(0.1f);

    //3
    Assert.True(game.isGameOver);

    Object.Destroy(game.gameObject);
}
  1. 생성된 운석의 위치를 우주선의 위치와 같게 합니다.
  2. 물리 엔진이 충돌을 판단하는데 시간이 걸리므로 0.1초를 기다립니다.
  3. gameOvertrue인지 확인합니다.

Test Runner 윈도우에 새로 추가한 함수가 유닛 테스트 목록에 나타납니다. 13

GameOverOccursOnAsteroidCollision 함수를 클릭하고 Run Selected를 누르면 선택된 함수만 실행됩니다. 14

Setup과 TearDown 추가

두 테스트 함수를 보면 game 게임 오브젝트를 생성하는 부분과 삭제하는 부분이 중복으로 들어가 있습니다.

GameObject gameGameObject = MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
game = gameGameObject.GetComponent<Game>();
...
Object.Destroy(game.gameObject);

대부분의 유닛 테스트에는 이와 같이 SetupTear Down 단계가 있는 경우가 많습니다. [Setup] Attribute은 매 유닛 테스트 함수를 실행하기 전에 준비작업을 위해, [TearDown] Attribute은 실행한 뒤 정리작업을 위해 사용합니다.

public class TestSuite
{
    private Game game;

    [SetUp]
    public void Setup()
    {
        GameObject gameGameObject = 
            MonoBehaviour.Instantiate(Resources.Load<GameObject>("Prefabs/Game"));
        game = gameGameObject.GetComponent<Game>();
    }

    [TearDown]
    public void Teardown()
    {
        Object.Destroy(game.gameObject);
    }

    [UnityTest]
    public IEnumerator AsteroidsMoveDown()
    {
        GameObject asteroid = game.GetSpawner().SpawnAsteroid();
        float initialYPos = asteroid.transform.position.y;
        yield return new WaitForSeconds(0.1f);
  
        Assert.Less(asteroid.transform.position.y, initialYPos);
    }

    [UnityTest]
    public IEnumerator GameOverOccursOnAsteroidCollision()
    {
        GameObject asteroid = game.GetSpawner().SpawnAsteroid();
        asteroid.transform.position = game.GetShip().transform.position;
        yield return new WaitForSeconds(0.1f);

        Assert.True(game.isGameOver);
    }
}

Setup에서 Scene을 불러와서 사용할 수도 있습니다.

    [SetUp]
    public void Setup()
    {
        SceneManager.LoadScene("GameScene");
    }

Test Driven Development (TDD)

테스트 코드를 먼저 작성한 다음에 프로그램을 작성하는 개발 기법입니다. 자세한 내용은 따로 검색해보시기 바랍니다.

Unit Test의 장점

  • 함수가 기대한 대로 동작할 거라는 확신을 줍니다.
  • 테스트 가능한 코드를 작성하게 됩니다.
  • 버그를 더 빠르게 찾아내고 고칠 수 있습니다.
  • 기존에 동작하던 코드에 업데이트로 인한 새로운 버그가 생기는 것을 방지할 수 있습니다. (regression bugs)

Unit Test의 단점

  • 프로그램 코드를 작성하는 것보다 테스트 코드를 작성하는 게 더 오래걸릴 수도 있습니다.
  • 틀린 테스트 코드로 인해 잘못된 확신이 들 수 있습니다.
  • 테스트 코드를 작성하려면 추가적인 지식이 필요합니다.
  • 코드의 중요한 부분들은 테스트하기 쉽지 않습니다.
  • private 클래스 함수들을 테스트할 수 없는 테스트 프레임워크도 있습니다.
  • 테스트 코드 유지보수에 시간이 많이 걸립니다.
  • 통합 모듈 에러 (integration error)를 찾아내기 어렵습니다.
  • UI는 테스트하기 힘듭니다.
  • 경험이 부족한 개발자는 이상한 걸 테스트하기 위해 시간을 낭비할 수 있습니다.
  • 외부 모듈이나 런타임 의존성이 있을 경우 테스트하기 어렵습니다.

마치며

이 튜토리얼이 도움이 되었다면 raywenderlich.com에 가입하고 이 글 Introduction To Unity Unit Testing에 별점을 매겨주세요.

Written with StackEdit.