{
"name": "GameAssembly",
"references": ["Unity.TextMeshPro", "UniRx"],
"optionalUnityReferences": ["TestAssemblies"]
}
테스트
개발 과정에서 여러 가지 테스트가 필요한 경우가 있다.
이러한 테스트를 간편하게 진행할 수 있게 해주는 Framework가 있다.
Test Runner
test Runner는 Unity에서 제공하는 Test Framework이다.
두 가지의 모드가 존재한다.
- play mode: 런타임에 사용할 기능 테스트(runtime)
- edit mode: 에디터에서 사용할 기능 테스트(static)
사용법
에디터에서 Test 하고 싶은 폴더를 선택하여 테스트 폴더를 생성한다.
이후, Assembly Definition을 생성한다.
그리고 생성된 폴더에서 Test Script를 작성한다.
Test Script는 Test Folder에서만 생성할 수 있다.
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
public class NewTestScript
{
// A Test behaves as an ordinary method
[Test]
public void NewTestScriptSimplePasses()
{
Assert.AreEqual(Calculator.Add(1,1), 2);
}
// A UnityTest behaves like a coroutine in Play Mode. In Edit Mode you can use
// `yield return null;` to skip a frame.
[UnityTest]
public IEnumerator NewTestScriptWithEnumeratorPasses()
{
// Use the Assert class to test conditions.
// Use yield to skip a frame.
yield return null;
}
}
EnumeratorPasses 는 런타임에 SimplePasses는 정적 타임에 실행하는 함수이다.
이후, TestRunner에서 Run All 혹은 Run Selected를 통해 테스트를 진행할 수 있다.
성공 | |
실패 |
실제 게임에 적용
Test Runner를 실제 게임에서 활용해 보자.
게임에 필요한 전체 Script를 하나의 폴더로 묶은 뒤 Test Folder를 생성한다.
GameTest라는 폴더가 Test Folder이다.
앞에서 진행한 것과 마찬가지로 Test Script를 생성한 뒤 테스트할 내용을 작성하면 된다.
하지만 Test Script에서 게임에 필요한 Script에 접근할 수 없기 때문에 하나의 Assembly로 묶어줘야 한다.
위의 사진에서 GameAssembly가 이에 해당한다.
이렇게 한다면 수많은 에러가 나올 수 있다.
이는 Script에서 작성한 코드 중 외부 라이브러리를 사용한 경우 접근을 하지 못하기 때문이다.
따라서, Assembly를 수정하여 접근할 수 있도록 설정해 주면 된다.
{
"name": "GameAssembly",
"references": ["Unity.TextMeshPro", "UniRx"],
"optionalUnityReferences": ["TestAssemblies"]
}
references에 사용한 라이브러리를 작성하면 된다.
Test Script 내용은 다음과 같다.
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using UnityEngine.UI;
public class GameTestScript
{
private float testTime = 0f;
[Test]
public void GameTestScriptSimplePasses()
{
}
[UnityTest]
public IEnumerator GameTestScriptWithEnumeratorPasses()
{
SceneManager.LoadScene("GameScene", LoadSceneMode.Single);
yield return WaitForSceneLoad();
var gameManager = GameObject.Find("GameManager").GetComponent<GameManager>();
Assert.IsNotNull(gameManager, "gameManager == null");
var roadManager = GameObject.Find("RoadManager").GetComponent<RoadManager>();
Assert.IsNotNull(roadManager, "roadManager == null");
var gasSpawner = GameObject.Find("GasSpawner").GetComponent<GasSpawner>();
Assert.IsNotNull(gasSpawner, "gasSpawner == null");
var startBtn = GameObject.Find("BtnStart").GetComponent<Button>();
Assert.IsNotNull(startBtn, "startBtn == null");
startBtn.onClick.Invoke();
yield return null;
var leftBtn = GameObject.Find("BtnLeft").GetComponent<Button>();
Assert.IsNotNull(leftBtn, "leftBtn == null");
var rightBtn = GameObject.Find("BtnRight").GetComponent<Button>();
Assert.IsNotNull(rightBtn, "rightBtn == null");
var vehicle = GameObject.Find("Vehicle");
Assert.IsNotNull(vehicle, "vehicle == null");
var inputController = GameObject.Find("Buttons").GetComponent<InputController>();
Assert.IsNotNull(inputController, "inputController == null");
yield return null;
while (gameManager.gameState == GameState.InGame)
{
testTime += Time.deltaTime;
yield return new WaitUntil(() => gasSpawner.gas != null);
var gasXPos = gasSpawner.gas?.transform.position.x;
if (vehicle.transform.position.x < gasXPos)
{
MoveButtonDown(rightBtn.gameObject);
MoveButtonUp(leftBtn.gameObject);
}
else if (vehicle.transform.position.x > gasXPos)
{
MoveButtonDown(leftBtn.gameObject);
MoveButtonUp(rightBtn.gameObject);
}
else
{
MoveButtonUp(leftBtn.gameObject);
MoveButtonUp(rightBtn.gameObject);
}
yield return null;
}
Debug.Log(testTime);
yield return null;
}
private IEnumerator WaitForSceneLoad()
{
while (SceneManager.GetActiveScene().buildIndex > 0)
{
yield return null;
}
}
private void MoveButtonDown(GameObject moveButton)
{
PointerEventData pointerEventData = new PointerEventData(EventSystem.current);
ExecuteEvents.Execute(moveButton, pointerEventData, ExecuteEvents.pointerDownHandler);
}
private void MoveButtonUp(GameObject moveButton)
{
PointerEventData pointerEventData = new PointerEventData(EventSystem.current);
ExecuteEvents.Execute(moveButton, pointerEventData, ExecuteEvents.pointerUpHandler);
}
}
필요한 Object들을 씬에서 불러와 변수로 선언한 뒤 while문 안에서 게임 플레이를 묘사한 것이다.
gas의 위치를 받아와 해당 위치로 이동하도록 최적의 플레이를 진행했을 때 상황을 가정했다.
테스트 코드를 짜는 것이 그리 쉽지는 않았다.
지금은 간단한 게임이라 x좌표만 비교하여 플레이를 짤 수 있었지만 게임이 복잡해지면 테스트 코드를 작성하는 것도 문제가 될 것이라 생각한다.