Swift Testing

김가영·2025년 3월 18일
0

swift

목록 보기
6/9

References

Introduction

  • Swift6~
  • 매크로를 사용하여 더 간편하게 테스트를 작성하는 방법
@Test("Continents mentioned in videos", arguments: [
  "A Beach",
  "By the Lake",
  "Camping in the Woods"
])
func mentionedContinents(videoName: String) async throws {
  let videoLibrary = try await VideoLibrary()
  let video = try #require(await videoLibrary.video(named: videoName))
  #expect(video.mentionedContinents.count <= 3)
}

Building blocks

Component1 ) Test Functions

import Testing
@Test func mentionedContinents() { ... }
  • @Test annotation을 가지는 함수
  • async/throws일 수 있음
  • global actor-isolated (@MainActor)일 수 있음
  • 함수 이름에 test.. 가 들어가지 않는 걸 권장한다.

Component2) Expectations

#expect(video.mentionedContinents.count <= 3)
  • 기존 XCTest에 존재하던 XCTAssert… 등의 여러 함수들이 #expect 하나로 간소화된다.
  • 기본적으로 내부 condition이 true 인지 판단하고, false 인 경우 좀 더 자세한 설명을 보여준다.

2.1 required Expectations

try #require(session.isValid)
// or
let method = try #require(payments.first)
  • false 또는 nil (when unwrapping optional)인 경우 테스트를 즉시 종료시킨다.
  • 일종의 early return

Component3) Traits

  • add descriptive information about a test
  • customize whether a test runs
  • modify how a test behaves

4. test suit

  • struct 로 test를 묶으면 테스트를 그룹화하여 관리할 수 있다. navigator에도 그룹화된 형태로 표시된다.
  • suits
    • group related test functions and suits
    • @Suit annotation을 통해 정의될 수도 있고
    • @Test 함수나 test suit을 포함하고 있는 타입들은 자동으로 suites가 된다.
    • stored instance property를 가질 수 있다.
    • init / deinit 을 사용해서 set-up/tear-down 로직을 수행하기도 한다.
    • @Test 함수마다 다른 suit instance가 생성된다. → 상태가 공유되지 않는다.

Common Workflows

특정 조건일때만 테스트하기

@Test(.enabled(if: AppFeatures.isCommentingEnabled))
func videoCommenting() { // ... }

@Test(.disabled("Due to a known crash"), // 왜 disable되었는지에 대한 설명을 추가할 수 있음
      .bug("example.org/bugs/1234", "Program crashes at <symbol>")) // 관련 이슈에 대한 url 추가
func example() { // ... }

@Test
@available(macOS 15, *) // 함수 내에 #available을 사용하는 것 대신 권장됨
func usesNewAPIs() { // ... }
  • 조건을 만족할 경우에만 테스트가 동작하게 할 수 있다.
  • CI에서 왜 테스트가 동작하지 않았는지 확인이 가능하기 때문에 함수 내에서 guard를 쓰는 것보다 효과적이다.

테스트에 태그 추가하기

@Test(.tags(.formatting)) func rating() async throws {
    #expect(video.contentRating == "G")
}
  • 테스트 함수나 테스트suit에 태그를 추가할 수 있다. (테스트 suit에 있는 태그들은 하위 suit/test functions에 상속된다.)
  • 네비게이터에서 groupByTag 옵션을 사용해서 해당 테스트들만 확인할 수 있고, 태그는 여러 프로젝트에서 공유될 수도 있다.

동일한 포맷의 테스트를 반복하기(parameterized test)

  • 동일한 포맷의 테스트를 반복적으로 작성해야 하는 경우가 있다. 이 때 parameterized test를 이용할 수 있다.
struct VideoContinentsTests {

    @Test("Number of mentioned continents", arguments: [
        "A Beach",
        "By the Lake",
        "Camping in the Woods",
        "The Rolling Hills",
        "Ocean Breeze",
        "Patagonia Lake",
        "Scotland Coast",
        "China Paddy Field",
    ])
    func mentionedContinentCounts(videoName: String) async throws {
        let videoLibrary = try await VideoLibrary()
        let video = try #require(await videoLibrary.video(named: videoName))
        #expect(!video.mentionedContinents.isEmpty)
        #expect(video.mentionedContinents.count <= 3)
    }

}
  • AS-IS
    struct VideoContinentsTests {
    
        @Test func mentionsFor_A_Beach() async throws {
            let videoLibrary = try await VideoLibrary()
            let video = try #require(await videoLibrary.video(named: "A Beach"))
            #expect(!video.mentionedContinents.isEmpty)
            #expect(video.mentionedContinents.count <= 3)
        }
    
        @Test func mentionsFor_By_the_Lake() async throws {
            let videoLibrary = try await VideoLibrary()
            let video = try #require(await videoLibrary.video(named: "By the Lake"))
            #expect(!video.mentionedContinents.isEmpty)
            #expect(video.mentionedContinents.count <= 3)
        }
    
        @Test func mentionsFor_Camping_in_the_Woods() async throws {
            let videoLibrary = try await VideoLibrary()
            let video = try #require(await videoLibrary.video(named: "Camping in the Woods"))
            #expect(!video.mentionedContinents.isEmpty)
            #expect(video.mentionedContinents.count <= 3)
        }
    
        // ...and more, similar test functions
    }
  • 단순한 for in loop 에 비해서 좋은 점
    • 각 argument의 detail에 대해 결과에서 볼 수 있다.
    • 병렬로 동작 가능하다.
    • debug시 특정 argument만 돌리는 것도 된다.

SwiftTesting and XCTests

  • XCTest는 엄청 많은 expectation 함수를 갖지만 SwiftTesting은 #expect 하나로 처리 가능하다.

Migration

  • 하나의 타겟에서 XCTest랑 SwiftTest가 둘 다 존재할 수 있다.

profile
개발블로그

0개의 댓글

관련 채용 정보