ved_Rony
article thumbnail

개요

테스트환경이 구축 되어있지 않으면, 어떤 기능에 대한 테스트를 하기 위해서 매번 처음부터 플레이를 해봐야 하는 상황이다. 개별코드 단위에 대한 분류도 잘 안되어 있을 가능성도 크다.

Why?

왜 테스트 환경을 만들기 꺼려할까?

  • 비즈니스 로직에 집중하는 시간도 부족한데 무슨 테스트를 작성하는가? → 따로 시간을 내서 작업을 안했으면, 손도 못 댔을 작업
  • 이것도 나중에 유지보수가 되어져야하니 부담스럽다 → 막연하게 느껴진다.
  • 귀찮다 → 하고나서 어떤 눈에 보이는 결과가 바로 있는 게 아님

왜 필요할까?

  • 버그를 조기에 잡아내는 것을 기본 목적
  • 단위 테스트를 사용한다면 연관 컴포넌트가 개발되지 않더라도 개발이 마무리 됬다고 증명
  • 테스트 스위트를 이용하여 모든 테스트를 동시에 실행
  • 단위 테스트는 리펙토링에 의해 기존의 코드가 망가지지 않는다는 것을 보장
  • 구현 코드의 품질 향상 기능
  • 구현 코드의 예제 소스 역할

How?

유니티에서 제공하는 test framework가 존재한다. 프레임워크보다는 에셋이라고 부르는 게 편하다. TestRunner라는 단위 테스트 환경을 제공한다.

  • edit mode : EditMode 테스트 중일 때 이를 저장할 Editor 폴더를 만들어야 합니다. Execute [UnityTestAttribute] in the [EditorApplication.update]callback loop when running in Edit mode.
  • play mode : 플레이 모드 테스트를 활성화하면 프로젝트 빌드의 추가 어셈블리가 포함되며 프로젝트의 크기와 빌드 시간을 늘릴 수 있습니다. 플레이 모드로 실행 중일 때 [UnityTestAttribute]를 코루틴으로 실행해야 합니다.

준비과정

1. TestRunner를 사용 하기 위해서는, assembly definition영역을 나눠줘야한다.
Assembly Definition
assembly definition이란?
assembly definition - dll 파일로, 이에 속한 파일들을 하나의 어셈블리로 나눠 준다. 기존에는 assembly -cysharp이라는 하나의 디폴트 어셈블리에 모든 스크립트가 포함되고, 그러다 보니 하나의 스크립트를 수정 할때마다, 모든 스크립트의 컴파일이 일어나, 시간이 더욱 오래걸린다. 규모가 커지는 프로젝트 일수록 치명적이다. 반면 커스텀 어셈블리로 나눠준다면, 편집된 스크립트 영역만 컴파일 되기에 속도가 준다.testrunner를 사용하기 위해서는 이 사용자 어셈블리가 필요한데, 이는 test와 실제 service 환경을 구분하기 위함이다.
→ 간단하게 정리하자면, 스크립트 파일들을 하나의 묶음으로 나눠주는 것. default로는 assembly-cysharp이라는 하나의 덩어리로 존재한다.



링크 : https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html

 

Unity - Manual: Assembly definitions

Assembly Definition properties Assembly definitions Assembly Definitions and Assembly References are assets that you can create to organize your scriptsA piece of code that allows you to create your own Components, trigger game events, modify Component pro

docs.unity3d.com

2. 코드 커버리지 적용

코드의 상태나 호출 빈도를 직관적으로 볼 수 있는 코드 커버리지가 코드 공부에 도움이 된다. 그전에는 호출이 안 되고 있겠다고 단순히 짐작하고 있던 걸 이제는 직접 확인할 수 있고, 주변 함수의 호출 횟수 등을 비교하며 심층적인 분석이 가능. 최적화도 쉬워짐. 코드 커버리지로 체크하면서 사용하지 않는

변수도 더욱 잘 보이게 됨.

 

3. 코드 작성

[UnityTest]
    public void FetchUserData_WhenUserIsAnonymous_ShouldSetStatusToSuccess()
    {
        // Arrange
        var userManager = new UserManager();
        userManager.SetUserAnonymous();
        var userData = new UserData(userManager);
        
        // Act
       userData.FetchUserData();
        
        // Assert
        Assert.AreEqual(UserDataRequestStatus.Success, userData.Status);
    }
    
    [UnityTest]
    public void FetchUserData_WhenUserIsNotAnonymous_ShouldLoadUserDataAndSetStatus()
    {
        // Arrange
        var userManager = new UserManager();
        userManager.UserToken = "valid_token";
        var userData = new UserData(userManager);
        
        // Act
        userData.FetchUserData();
        
        // Assert
        Assert.AreNotEqual(UserDataRequestStatus.UnknownError, userData.Status);
    }
    
    [UnityTest]
    public void FetchUserData_WhenLoadingUserDataThrowsException_ShouldSetStatusToUnknownError()
    {
        // Arrange
        var userManager = new UserManager();
        userManager.UserToken = "invalid_token";
        var userData = new UserData(userManager);
        
        // Act
        userData.FetchUserData();
        
        // Assert
        Assert.AreEqual(UserDataRequestStatus.UnknownError, userData.Status);
    }

→ 추가로, 여러가지 테스트케이스가 존재한다면, 위의 메소드에 패러미터를 추가해 놓고, 메소드위에 [test case]키워드 추가 해주면 된다.

→ 비동기로 api request를 테스트 할 경우 → play mode에서 Unitask를 코루틴으로 전환하여 실행한다.

[UnityTest]
[TestCase("abc@aa.aa", "https://test.tester", ExpectedResult = true)]
public IEnumerator PostResetLink(string email, string link) => UniTask.ToCoroutine(async () => {
        var apiRequest = new MakeApiRequest(url.devUrl)
            .PostAsync(url.userResetUrl);

        var result = await Sender(apiRequest);

        Assert.AreEqual(true, result);
    }
);

 

테스트 환경을 빌드에 포함 안시키는 법?

인스펙터 창에 Define_Constraints에 UNITY_INCLUDE_TESTS 키워드가 추가 되어있으면, 자동으로 테스트 어셈블리를 player build에서 제외 시켜준다. 테스트 어셈블리를 생성하자마자 자동으로 생성된다.

 

Mock, Stub?

mock 과 stub/spy : 정확한 정의는 아니다. 하지만, 이해를 쉽게 하자면 아래와 같이 예시를 들수 있다.

  • mock 행동 → 메소드가 몇번 불렸는지
  • stub 상태 → 결과값이 어떻게 나오는 지
  • spy 추적 → 이벤트가 어디서 일어 났는지 등 추적

 

싱글턴을 mock 하려면?
 https://medium.com/@martinrybak/how-to-mock-singletons-and-static-methods-in-unit-tests-cbe915933c7d 

 

How to Mock Singletons and Static Methods in Unit Tests

So you’ve had enough. That aging codebase of yours is just too darn fragile to work on. Everytime you fix a bug or add a new feature…

medium.com

→ 정의 하자면, 인터페이스 주입 시킨후 싱글턴을 사용 하는 곳에서 인터페이스를 목업 해준다.

 

유니티에서 mock 하려면? 
 https://www.youtube.com/watch?v=enwxxffhvHQ

→ 영상에서 나와있는 데로 따라하면 된다. moq를 다운 받을수는 있다. nuget or package로 가능하다. pose는 못한다고 한다. 적용후 성공사례는 보지 못했다.

 

유니티에서 mocking 하려는 툴?

https://forum.unity.com/threads/any-proper-mocking-frameworks-for-unity-unit-testing.1366350/ 

 

Question - Any proper mocking frameworks for Unity unit testing?

Coming from an enterprise software background I am discouraged by the lack of mocking ability when running unit tests in Unity (2022.2.4). I tried to...

forum.unity.com

→ Xarborough의 답글을 보면, 결국, 인터페이스 분리 법칙을 잘 사용하고, 정말 테스트하고자 하는것이 무엇인지 파악해야한다.

 

[기본적인 mock 사용법]

[적용]

[UnityTest]
public IEnumerator LoadAsset(){
	Model lobby = new Model();

		var mock = new Mock<Manager>();
       mock.Setup(x => x.LoadData());

    return UniTask.ToCoroutine(
        async () => {            await lobby.FetchData(mock.Object);					 mock.VerifyAll();        });
}

public async UniTask FetchData(Manager user) {
        try {
            var status = await user.LoadData();
        } catch (Exception ex) {
            Debug.LogError(ex.Message);
        }
    }

주의사항

  • mocking으로 내부 구현한 메소드가 불렸는지, 몇번 불렸는지에 대한 verification이 종종 쓰인다. 이를 지양해야한다. 여러가지 이유가 있다.
  • 내부 구현이 바뀔때 테스트 코드도 동일하게 다 바꿔줘야 한다. 이는 상당한 리소스가 낭비가 된다.
  • 코드 커버리지를 채우려고 억지로, 구현하는 경우가 있다. 그럴 필요가 없다.
  • 외부 디펜던시를 모킹하기 위해선 상관이 없지만, 내부 구현 코드에서 어떻게 호출되고 있는 지 확인 하기 위한 모킹은 비효율적이다.
  • chat gpt를 적극 활용하자. 100프로 정확한 코드는 아니지만, 기본적인 틀이 짜여지고, 그 테스트코드위에서 더하거나 빼는 식으로 개발을 할수 있기 때문에, 상당한 개발 시간을 아낄수가 있다.
  • 결과 지향적인 테스트를 짜자.
  • 의존성 주입은 젠젝트 같은 툴이 유니티에는 잘만들어져 있다. 잘 활용하도록 하자.
profile

ved_Rony

@Rony_chan

검색 태그