목차

티스토리 뷰

728x90
반응형

안녕하세요 🐾 

복기를 위한 XCTest에 대한 기본 개념 정리글 입니다.

혹시나 틀린 부분이 있다면 알려주세요!

 

※ Quick / Nimble에 대한 정보는 아래의 글을 참고해주세요.

2024.03.21 - [iOS/Xcode] - [iOS/Xcode] XCTest 관련 라이브러리 정리(Quick, Nimble)

 


 

 

1. XCTest

1.1 XCTest / XCTestCase

XCTest란 Xcode에서 단위 테스트, 성능 테스트, UI 테스트를 지원하는 프레임워크입니다.

해당 프레임워크의 구조부터 간략히 살펴보겠습니다.

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

 

XCTestCase의 정의를 살펴보면 아래와 같이 XCTest서브클래싱하고 있는 것을 볼 수 있습니다.

XCTest는 테스트 생성, 관리 및 실행을 위한 추상적 클래스입니다.

 

 

프로젝트에서 테스트를 위한 파일을 생성하고 기본적으로 완성되어있는 클래스를 보면,

아래와 XCTestCase를 상속받아 두가지 클래스(XCTestCase, XCTest)를 서브클래싱하여 사용할 수 있게 됩니다.

import XCTest

final class AssumXCTestTests: 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.
    }

}

 

XCode 11.4 버전부터 테스트 클래스 파일을 생성하면 기본으로 위와 같이 setupWithError(), tearDownWithError()가 생성됩니다.

XCTest 내 메소드 콜 순서는 setUpWithError()setUp() → tearDown() → tearDownWithError() 입니다.

 

테스트 메소드 호출 전에 호출되는 것이 setUpWithError, setUp, 

테스트 메소드 호출 후 호출되는 것이 tearDownWithError, tearDown 입니다. 

 

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

 

 

 

1.2 테스트 파일 생성 방법

 

프로젝트 생성시 : New Project →  Include TestsNextCreate

프로젝트 생성시 테스트 타겟 추가

 

프로젝트 생성시 추가하지 않은 경우 :   프로젝트선택 - Editor - Add Target - Unit Test Bundle 생성

프로젝트 내 별도의 테스트 타겟 추가

 

테스트 파일 생성 후 초기화면

 

1.3 테스트 케이스 생성 예제

예를 들어, 프로젝트 내 아래와 같은 구조로 되어있는 Calculator의 더하기 기능에 대한 테스트 케이스를 작성하고자 합니다.

 

이에 대한 테스트 케이스 작성코드를 아래와 같이 입력한 후 마름모 모양의 아이콘을 누르면 테스트가 진행됩니다.

 

위 사진에서 '테스트 결과 확인' 주석줄과 같은 코드를 Test Assertion이라고 합니다. 어써션이란 개발자가 반드시 참이라고 생각되는 사항을 표현하는 논리식입니다.

 

만약 테스트를 실행하였더니 Sandbox: rsync deny~와 같은 에러가 발생하면 아래의 더보기 버튼을 눌러 설정을 통해 해결합니다.
더보기

프로젝트 설정- Build Options - User Script Sandboxing - No

 

1.4 Test Assertion

테스트를 수행했을 때 수행한 테스트의 결과값이 의도한대로 나왔는지 확인하기 위해 XCTest 프레임워크에 정의되어있는 Test Assertion을 사용합니다. Test Assertion의 종류 및 사용법은 간략히 아래와 같습니다.

// 조건이 참인지 거짓인지 확인
XCTAssertTrue(expression: Bool, message: String)
XCTAssertFalse(expression: Bool, message: String)
// expression: 테스트 대상 조건
// message: 실패 시 출력 메시지

// 두 값이 같은지 다른지 확인
XCTAssertEqual(expression1: T, expression2: T, message: String)
XCTAssertNotEqual(expression1: T, expression2: T, message: String)
// expression1 테스트 대상 실제 값
// expression2: 예상 값
// message: 실패 시 출력 메시지

// 두 실수 값이 지정한 정확도 범위 내에 있는지 확인
XCTAssertEqual(expression1: T, expression2: T, accuracy: T, message: String)
XCTAssertNotEqual(expression1: T, expression2: T, accuracy: T, message: String)
// expression1 테스트 대상 실제 값
// expression2: 예상 값
// accuracy: 허용 오차 범위(expression1 과 expression2의 최대 차이)
// message: 실패 시 출력 메시지

// 값이 nil인지 아닌지 확인
XCTAssertNil(expression: Any?, message: String)
XCTAssertNotNil(expression: Any?, message: String)
// expression: 테스트 대상 값
// message: 실패 시 출력 메시지

 

Test Assertion에 대한 문서를 확인하려면 링크를 참고해 주세요.

 

 

2. 테스트 개발 방법론 - TDD, BDD

앞선 예제를 보면서, XCTest는 테스트를 위해 크게 아래와 같은 일련의 과정을 겪었습니다.

  1. 테스트 대상(System Under Test) 정의
  2. SUT에 대한 테스트 케이스 작성
  3. 예상된 동작에 따른 결과 확인

그리고 이 과정을 효율적으로 수행하기 위한 대표적인 개발 방법론으로 TDD, BDD 개발 프로세스가 있습니다.

 

2.1 테스트 주도 개발(TDD, Test Driven Development)

설계/기획 이후 먼저 테스트를 위한 코드를 작성한 후 테스트를 거친 뒤 개발 코드를 작성합니다.

TDD 개발 프로세스는 실패 - 성공-  리팩토링 짧은 주기를 반복하여 올바른 결과를 도출합니다.

 

만약 계산기 기능을 사용하는 사용자에게 1과 2를 더한 결과를 알려줘야 한다라는 요구사항이 있다는 전제하에 TDD로 테스트 케이스를 진행한다면 어떨까요?

 

[ Red ]

실패할 테스트 코드를 작성,  원하는 동작이 실패하는 테스트 케이스를 작성합니다.

func test_add() {
    let result = add(first: 1, second: 2) // 미구현된 코드
    
    // 두 값이 같거나 다른지 확인
    XCTAssertEqual(addResult, 3, "두 값이 일치하지 않습니다.")
}

 

위 예제 코드에 대한 테스트를 수행하면 아직 add 메소드에 대한 구현이 없기때문에 아래와 같이 의도된 대로 실패하는 테스트가 실행됩니다.

 

 

[ Green ]

실패한 테스트를 통과하기 위해 최소한의 코드를 적용합니다. 테스트를 통과하는 것이 목표입니다.

func add(first: Int, second: Int) -> Int {
    return first + second
}

func test_add() {
    let addResult = add(first: 1, second: 2)

    // 두 값이 같거나 다른지 확인
    XCTAssertEqual(addResult, 3, "두 값이 일치하지 않습니다.")
}

 

 

테스트 통과를 위한 add 메소드를 추가한 뒤 테스트 진행 성공을 확인합니다.

 

[ Refactor ]

테스트의 성공을 보장하면서, 코드의 품질(가독성, 유지보수성)을 개선하는 단계입니다. 수정 혹은 개선사항이 있다면 반영하고 효율적인 코드로 개선해나가는 과정입니다.

func add(first: Int, second: Int) -> Int {
    return first + second
}

func test_add() {
    let x = 1
    let y = 2
    let addResult = add(first: x, second: y)

    // 두 값이 같거나 다른지 확인
    XCTAssertEqual(addResult, x + y, "두 값이 일치하지 않습니다.")
}

 

 

TDD는 위 예처럼 '기능'을 확인하기 위한 목적으로 코드가 작성됩니다.

Red-Green-Refacoring 의 과정을 계속 반복해가며 처음부터 복잡한 로직의 코드를 작성하기보다 점진적으로 코드를 확장해 나가는 것을 추구합니다.

 

TDD는 아래와 같은 장•단점이 있습니다.

 

장점

  • 에러 발생 위험이 낮은 안정적인 코드 작성이 가능합니다.
  • 코드를 작은 단위로 분리하여 테스트하여 리팩토링이 수월해집니다.
  • 각 모듈을 부분적으로 확인할 수 있어 유지보수가 용이합니다.

 

단점

  • 테스트 코드 작성에 의한 개발공수가 증가합니다.
  • 코드의 변경에 따라 다수의 테스트 케이스가 수정되거나 추가 될 수 있습니다.
  • 복잡한 시나리오나 성능은 테스트하기 어려울 수 있습니다.

 

 

2.2 행위 주도 개발(BDD / Behavior Driven Development)

기능 중심의 테스트케이스를 작성하는 것이 TDD라면, BDD사용자의 행위를 기반으로 사용자 시나리오를 구성한 뒤 테스트 케이스를 만들고 개발을 진행하는 방식입니다.

 

사용자 시나리오요구사항이나 기획서에 있는 내용을 의미하며, 시나리오는 G-W-T, Given(시나리오 정의), When(시나리오 조건), Then(시나리오 예상 결과) 구조를 갖는 것을 권장합니다.

 

만약 계산기 기능을 사용하는 사용자에게 1과 2를 더한 결과를 알려줘야 한다라는 요구사항이 있다는 전제하에 BDD로 테스트 케이스를 진행한다면 어떨까요?

 

G-W-T

  • Given - 시나리오 정의 / 주어진 환경
  • When - 시나리오 조건 / 행위
  • Then - 시나리오 예상 결과  / 기대결과
enum Sign {
    case plus, minus, multiply, divide
}

func calculation(first: Int, second: Int, sign: Sign) -> Int {
    switch sign {
    case .plus:
        return first + second
    case .minus:
        return first - second
    case .multiply:
        return first * second
    case .divide:
        return first / second
    }
}

func test_add() {
    // Given : 주어진 환경
    let x = 1
    let y = 2

    // When : 동작/행위
    let addResult = calculation(first: x, second: y, sign: .plus)

    // Then : 동작에 따른 기대 결과
    XCTAssertEqual(addResult, x + y, "두 값이 일치하지 않습니다.")
}

 

사용자가 1과 2의 숫자가 주어진 환경에서(Given), 

사용자가 더하기 버튼을 선택 했을때(When),

기대하는 결과값이 나오는지 확인합니다.(Then),

 

만약 사용자가 빼기, 곱하기, 나누기 버튼을 선택했다면 올바른 기대값이 나오지 않겠죠?

BDD는 위 예처럼 사용자의 행위에 대한 동작과 결과를 테스트 케이스로 작성해보고 이를 확인하기 위한 관점으로 코드를 작성합니다.

 

BDD 또한 TDD에서 파생된 개발 방법론이기 때문에 장•단점은 TDD와 대동소이하나,

아래와 같은 차이점이 있다고 생각합니다.

 

장점

  • 예외 상황에 대한 모든 케이스를 생각하고 반영하는 TDD보다 요구에 따른 사용자 행위에 대한 테스트 케이스만 작성하므로 TDD보다 개발 시단이 단축 될 수 있다.

 

단점

  • 사용자 행위에 대한 정확한 이해를 바탕으로 테스트 케이스를 작성해야 한다.

 

 

3. 결론

XCTest 프레임워크를 사용하여 테스트를 진행할 때 사용되는 개발 방법론인 TDD, BDD 중 하나만 채택하여 사용하여도 무방하나, 이 둘은 서로 상호보완적 관계라고 생각합니다.

 

실제 프로젝트에서 테스트를 진행한다고 가정하였을 때,  먼저 BDD의 테스트 케이스로 시나리오에 대한 검증을 수행하고,  검증된 테스트 시나리오에서 사용되는 각 모듈들은 TDD의 테스트 케이스로 검증을 진행하는 것이 각자의 장점을 취합하는 것이 좋은 예가 아닐까 생각됩니다! 다만, 시간이 충분하다면요..😢

 

추가적으로 XCTest를 사용할 때 바늘과 실처럼 따라오는 라이브러리가 QuickNimble 입니다.

Quick은 BDD 기반의 테스트 케이스 생성을 도와주는 라이브러리, Nimble은 Test Assertion을 가독성있게 사용할 수 있게 도와주는 라이브러리 입니다.

 

다음 포스팅에서는 이 라이브러리들에 대하여 알아보도록 하겠습니다.

부족한 글 읽어주셔서 감사합니다!

 

 

4. 참고

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

- https://github.com/topics/test-driven-development

- https://github.com/topics/behavior-driven-development

- https://en.wikipedia.org/wiki/Behavior-driven_development

- https://en.wikipedia.org/wiki/Test-driven_development

반응형
댓글
300x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함