[Xcode] XCTest 기본 개념 정리

Luke·2024년 5월 5일

TestCode

목록 보기
5/5

Xcdoe의 XCTest에 대해서 간단하게 개념을 정리하기 위해 이글을 작성합니다.


XCTest / XCTestCase

XCTest 프레임워크는 크게 XCTest, XCTestCase 두 개의 클래스로 이루어져 있습니다.

XCTest

  • 테스트 생성, 관리, 실행하기 위한 추상 베이스 클래스입니다.
  • 각 테스트 메서드들이 불리기 전/후에 호출되는 setUp()과 teardown() 인스턴스 메서드가 정의되어 있습니다.
  • Xcode에서 단위 테스트, 성능 테스트, UI 테스를 지원하는 프레임워크입니다.

XCTestCase

  • XCTestCase의 정의를 살펴보면 XCTest를 서브클래싱하고 있는 것을 볼수 있습니다.
  • XCTest를 상속하며, 클래스(하나의 테스트 케이스) 안의 모든 테스트 메서드들이 불리기 전/후에 호출되는 setUp / teardown 인스턴스 메서드가 정의되어 있습니다.

그렇다면 setUp() 과 tearDown() 은 무엇을 해주는 메서드 일까? 이름에서 유추할수 있듯이 setUp() 은 테스트 케이스에 있는 각각의 테스트 메서드를 실행하기 전에 모든 상태를 reset해주는 함수이다. tearDown() 은 테스트 있는 각각의 테스트 메서드들이 끝나고 난 뒤에 정리 작업을 수행해주는 함수이다.

Xcode 11.4 버전부터 Unit test를 생성하면 기본적으로 setUpWithError() , tearDownWithError() 로 생성이 된다. 물론 setUp()과 tearDown()도 override 해서 사용할 수 있다.

XCTest의 호출 순서 : setUpWithError() → setUp() → tearDown() → tearDownWithError()

그렇다면 setUp, tearDown 과 차이점은 무엇일까?

차이점은 바로 함수명에서 나와있듯이 바로 Error를 던질 수 있게 바뀌었다는 점만 빼면 나머지는 다 동일하다고 한다.

즉, 코드 중에서 throws를 할 수 있는 코드들을 do-catch문이나, try? 를 사용하지 않고 try를 사용하여 setUpWithError()에서도 error를 throw하게 해준다는 것이다.

setUpWithError()에서 error를 던지지 않으면 정상적으로 실행되겠지만, error가 존재하는 경우에는 테스트 자체가 skip되게 된다. 따라서 테스트는 전부 실패한 것으로 나오게 되지만 실제로는 실행이 아예 되지 못한 것이다. setUpWithError가 호출되고 에러 코드가 나오고 그 이후에 tearDownWithError도 호출이 되게 된다.

그 외 테스트 진행을 위해 숙지해야 할 XCTestXCTestCase에 대한 프로퍼티 및 메서드는 애플문서를 참조해 주세요. 


실행 과정

프로젝트 생성 및 기본 설정

테스트 진행 과정을 알아보기 위해 프로젝트를 만들어 보겠습니다.

import Foundation

final class Calculator {
  private var number: Int
  
  init(with number: Int) {
    self.number = number
  }
  
  var numberDescription: String {
    return "The number is: \(number)"
  }
  
  func multiply(by otherNumber: Int) {
    number *= otherNumber
  }
  
  func divide(by otherNumber: Int) {
    number /= otherNumber
  }
}
import XCTest

final class CalculatorTests: XCTestCase {
  
  // 테스트 메소드를 실행하기 위한 객체 생성
  override func setUpWithError() throws {
    // Put setup code here. This method is called before the invocation of each test method in the class.
  }
  
  // 테스트한 객체 해제
  override func tearDownWithError() throws {
    // Put teardown code here. This method is called after the invocation of each test method in the class.
  }
  
}

기본적으로 테스트할 코드를 작성한 다음, 테스트 케이스에서 테스트할 모듈을 임포트해야 합니다.

import XCTest
@testable import Calculator

임포트할 때, @testable 어노테이션을 명시해야 합니다. @testable을 명시하지 않으면, 기본적으로 internal로 지정되어 모듈 안에서만 이용할 수 있으며 다른 모듈에서는 접근할 수 없습니다. 유닛 테스트를 위해서는 테스트할 모듈의 코드를 유닛 테스트 모듈에서 접근할 수 있어야 합니다. 프로덕트 모듈을 임포트할 때 @testable 어노테이션을 붙이면, 유닛 테스트 타겟이 internal인 클래스나 함수 등에 접근할 수 있게 됩니다. @testable 어노테이션을 붙이고 프로덕트 모듈을 한 번 컴파일한 후부터 테스트 모듈에서 프로덕트 모듈을 인식합니다.

주의사항: 해당 액세스(private 또는 fileprivate)는 접근할 수 가 없습니다. 해당 코드는 비공개로 되어있으므로 테스트 해서는 안됩니다. 비공개 코드를 테스트하고 싶다면 아키텍처 설계를 다시한번 더 고려해볼 필요가 있습니다.


테스트 코드 작성

위에서 선언한 Calculator 클래스의 곱하기와 나누기 기능을 테스트하는 코드를 작성해 보겠습니다.

이전에 설명했던 대로 setUp() 인스턴스 메서드는 각 메서드가 호출되기 전에 호출되므로, 테스트할 클래스를 초기화하는 코드를 넣는데, 이번 예제에는 sut(테스트할 대상)의 인스턴스에 값을 설정하여 생성했으므로 setUp 코드를 생략하겠습니다.

import XCTest
@testable import Calculator

final class CalculatorTests: XCTestCase {
  
  /// - Note: 테스트할 대상
  let sut = Calculator(with: 10)

  /// - Note: 테스트 메서드는 XCTestCase 서브클래스의 인스턴스 메서드로 작성하며, 파라미터와 리턴값이 없습니다. 
  /// 또한 소문자 단어 test로 시작하는 이름이어야 합니다
  func test_multiply_case() {
    sut.multiply(by: 6)
    XCTAssertEqual(sut.numberDescription, "The number is: 60")
  }
  
  func test_divide_case() {
    sut.divide(by: 2)
    XCTAssertEqual(sut.numberDescription, "The number is: 5")
  }
  
}

각 테스트 메서드들은 작성된 코드를 실행하고, 특정 조건이 맞는지 검사합니다. 이 때 XCTest 프레임워크에 포함된 XCTAssert 함수들을 이용합니다. 이 함수들을 이용한 문장을 test assertion이라고 합니다. 여기선 여러가지 XCTAssert 함수들 중 입력되는 표현식 두 가지가 같은지를 검사하는 XCTAssertEqual을 이용하였습니다

XCTAssert 메서드

위의 XCTAssertion을 보다보면 모두 XCTAssertTrue로 사용할 수 있다고 생각할지 모릅니다. 즉, 이 두 코드는 동일한 결과를 얻기위한 메서드입니다.

XCTAssertTrue(2 == 3)
XCTAssertEqual(2, 3)

하지만, 두 가지 상황을 염두에 두어야합니다.

첫째, 훌륭한 테스트가 전반적인 문서 작업의 일부로 작동하므로 의도를 보다 분영하게 표현할 기회가 있다면 아래 식으로 사용하는것을 권장합니다.

둘째, 테스트가 실패 할 경우 두 개의 Assertion이 서로 다른 오류 메시지를 내봅니다.

  • XCTAssertTrue failed
  • XCTAssertEqual failed: (“2”) is not equal to (“3”)

보기에도 두번째 것이 오류가 더 직관적입니다. 따라서 두번째 방식으로 쓰는것이 훨씬 더 가치있다고 생각합니다. 따라서 XCTest가 제공하는 가장 정확한 Assertion을 사용하도록 항상 염두해 두어야 합니다.


실행 결과

  • 테스트 성공

  • 테스트 실패 (로직을 다시 작성해야 됩니다.)


참고

https://blog.interactord.io/2019/07/17/%ed%85%8c%ec%8a%a4%ed%8a%b8%ec%9d%98-%ea%b8%b0%ec%b4%88-1/
https://velog.io/@minni/TDD%EC%99%80-Swift-XCTest-%ED%99%9C%EC%9A%A9%ED%95%98%EA%B8%B0
https://developer.apple.com/documentation/xctest/xctestcase
https://developer.apple.com/documentation/xctest
https://seizze.github.io/2020/01/08/%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%ED%85%8C%EC%8A%A4%ED%8C%85%EA%B3%BC-%EC%8A%A4%EC%9C%84%ED%94%84%ED%8A%B8%EC%97%90%EC%84%9C%EC%9D%98-Unit-Testing.html

0개의 댓글