티스토리 뷰

Swift&iOS/iOS

[iOS] Unit Test

sh9404 2023. 3. 30. 00:34

UnitTest?

개발자가 작성한 프로그램이 의도한 대로 동작하는지 검증하는 단위의 테스트 

-> 소스코드의 기본 동작을 검증할 수 있음으로 신뢰할 수 있는 프로그램 작성 가능

-> 특정 모듈, 비즈니스 로직 리팩토링시에도 테스트 함수를 통과해야 하므로 신뢰할 수 있는 프로그램 작성 가능

 

테스트 코드 작성 방법

함수(비즈니스 로직)에 대한 input case와 output case들을 정의하고 테스트를 통과하는지 검증하는 테스트 코드를 작성

동일한 input에 대해서는 동일한 output이 보장되어야만 함

만약 덧셈, 뺄셈이 가능한 계산기 프로그램을 개발한다고 했을 때 아래와 같이 덧셈 뺄셈에 대한 비즈니스 로직을 설계할 수 있습니다. 

Tests file로 가보시면, 아래와 같이 처음 보실 수도 있는(?) 여러 메서드가 선언되어 있는데요. 각각 어떤 목적을 갖는 함수들인지는 밑에서 살펴보는 걸로 하고! 우리의 목적은 테스트 함수 작성이기 때문에 테스트 함수를 작성해 볼 겁니다. 

테스트 함수는 무조건 test로 시작해야 합니다. test로 시작하는 함수를 만들면 위 이미지 행 라인 쪽 숫자대신 다이아몬드 버튼으로 바뀌게 되는데, 테스트 실행은 command + U로 전체 유닛 테스트를 진행할 수도 있고 저 다이아몬드 버튼을 눌러서 개별 비즈니스 로직을 테스트할 수도 있습니다. 

다시 돌아와서, 뷰모델의 두 비즈니스 로직 add와 minus가 문제 없는지를 테스트하기 위해 테스트 코드를 작성합니다. 테스트 코드는 given, when, then이라고 하는 구조적 유닛테스트 작성 방법이 있는데, 간단히 설명하자면

  • given: dependencies(주어지는 환경)
  • when: 테스트의 동작
  • then: 테스트 결과

로 이해할 수 있습니다. 만약 우리가 작성한 덧셈 뺄셈 테스트의 경우에는 아래처럼 볼 수 있겠죠? 

given: 두 개의 숫 자가 주어지고

when: 두 숫 자를 더하면(빼면)

then: 숫자를 더한 값(뺀 값)이 return 된다.

테스트를 작성하면 아래처럼 작성할 수 있습니다.

final class CalculatorTests: XCTestCase {
    let calculatorViewModel = CalculatorViewModel()
    
    override func setUpWithError() throws { }

    override func tearDownWithError() throws { }

    func testExample() throws { }

    func testPerformanceExample() throws {
        // This is an example of a performance test case.
        self.measure {
            // Put the code you want to measure the time of here.
        }
    }
    
    func testAdd() throws {
        // given
        let inputs: [(lhs: Int, rhs: Int)] = [(1, 1), (1, 2), (10, 5), (10, 10), (99, 1)]
        let outputs = [2, 3, 15, 20, 100]

        // when
        inputs.enumerated().forEach { idx, inputs in
            let result = calculatorViewModel.add(lhs: inputs.lhs, rhs: inputs.rhs)

            // then
            XCTAssertEqual(result, outputs[idx])
        }
    }

    func testMinus() throws {
        // given
        let inputs: [(lhs: Int, rhs: Int)] = [(5, 2), (10, 3), (9, 5), (10, 10), (100, 1)]
        let outputs = [3, 7, 4, 0, 99]

        // when
        inputs.enumerated().forEach { idx, inputs in
            let result = calculatorViewModel.minus(lhs: inputs.lhs, rhs: inputs.rhs)

            // then
            XCTAssertEqual(result, outputs[idx])
        }
    }
}

 

XCTest framework는 다양한 assertion들을 제공해 주는데 XCTAssertEqual은 그중 하나입니다. 아래 문서에서. 확인할 수 있듯이 두 개의 값이 같은지 아닌지 판단하고 같으면 테스트를 pass하게 되고 그렇지 않다면 fail이 됩니다. 

https://developer.apple.com/documentation/xctest/xctassertequal

이렇게 작성을 다 마친 후 command + U를 눌러서 테스트를 실행해 보면, 아래와 같이 초록색 체크박스로 테스트를 통과하는 것을 확인할 수 있습니다. 

만약 testMinus의 outputs의 맨 마지막을 100으로 바꾼다면..? 계산 값 99와 결괏값 100은 서로 다르니 아래처럼 테스트에 실패하게 됩니다. 

사실, add와 minus 함수의 경우 로직이라고 말할게 없어서 테스트가 필요할까 싶지만, 이건 설명을 위해서 그렇고, 보통의 경우에는 비즈니스 로직이 간단하지 않은 경우가 많기 때문에 테스트코드 작성이 의미 있습니다. 다양한 테스트케이스에 대해 우리가 설계한 비즈니스 로직이 테스트를 통과한다면 그 비즈니스 로직은 우리가 신뢰할 수 있게 되는 거니까요 ㅎㅎ, 테스트 케이스는 다양한 엣지케이스가 많으면 좋겠죠? (우리가 알고리즘 문제에서 마주하는 엣지케이스들 처럼요)

다시 돌아와서, 기본적으로 선언되어 있는 메서드들을 살펴보겠습니다.

setUpWithError()는 테스트가 실행되기 전 실행되는 메서드로 테스트 케이스를 실행하기 전 상태값 리셋등의 초기화 코드를 작성하게 됩니다. 우리가 일반적으로 init에 작성하는 코드를 작성한다고 생각하면 이해하기 쉽습니다.

tearDownWithError()는 모든 테스트가 끝난 후 실행되기 때문에, 테스트 실행을 모두 마친 후 실행되어야 할 코드를 작성하는 곳 입니다. 

Test Assertion의 경우 우리가 사용한 XCTAssertEqual 외에도 여러 Assertion을 제공해주고 있기 때문에 아래 문서에서 확인해 보시는 걸 추천드립니다.

https://developer.apple.com/documentation/xctest

그래도 기본적인 몇 가지 Assertion들을 소개하자면,

두 값이 같은지 다른지를 판단하는 XCTAssertEqual, XCTAssertNotEqual 

값이 nil인지 아닌지 판단하는 XCTAssertNil, XCTAssertNotNil

값이 큰지 작은지 판단하는 XCTAssertLessThan, XCTAssertGreaterThan

등이 있습니다. 

 

마지막으로, 만약 비동기 함수에 대해 테스트를 해보고 싶다면 어떻게 할까요? 

비동기 함수가 어떻게 작성되어 있느냐에 따라 테스트 방법이 조금 달라지게 되는데, 일반적인 completon Handler를 사용한 비동기 함수 라면 아래처럼 테스트해 볼 수 있습니다. (아래 코드는 wwdc 2021 async/await 영상에 나오는 코드 일부를 캡처했습니다.)

이 테스트가 통과하려면 두 가지를 만족해야 합니다. 5초 안에 비동기 함수의 응답이 들어와야 하고, 들어온 응답의 결과의 사이즈가 서로 같아야만 하죠. 5초 안에 비동기 함수 응답이 들어오지 않아도 테스트는 실패이고 사이즈가 같지 않아도 실패입니다.

만약 fetchThumbnail 함수가 Swift Concurrency를 사용한 비동기 함수라면 아래처럼 훨씬 더 간단하게 작성 가능 합니다. 

 

이 글이 Unit Test를 이해하는데 많은 도움이 되었길 바라며..! 다음 포스팅으로 찾아오겠습니다 :) 

댓글
링크
최근에 올라온 글
최근에 달린 댓글