안녕하세요! 피피아노입니다 🎵
이번 포스팅에서는 WWDC24에서 발표한 새로운 테스트 도구인 Swift Testing에 대해서 공부를 하고 정리를 해보려고 합니다.
Swift Testing은 새로운 오픈 소스 패키지로, Swift 코드를 테스트할 수 있습니다. 테스트를 설명하고 구성하는 기능이 포함되어 있으며, 장애 발생 시 대처를 위한 세부 정보를 제공하고 대규모 코드베이스에 맞게 확장할 수 있습니다.
프로젝트에 Swift Testing 도입하기
앱에 대한 테스트를 작성해 본 적이 없다면 먼저 프로젝트에 테스트 번들 대상을 추가해줘야 합니다.
File > New > Target을 선택합니다.
그런 다음 Test 섹션에서 Unit Testing Bundle을 검색합니다.
Swift Testing은 이제 Xcode 16부터 테스트 시스템의 디볼트 선택이 됩니다.
Swift Testing의 특징
Swift 기능 활용
- Swift용으로 만들어져 동시성 및 매크로와 같은 최신 기능 도입
- async/await 및 액터 격리 지원
- Swift 매크로를 활용한 자세한 실패 결과 제공
크로스 플랫폼 지원
- Linux와 Windows 등 모든 주요 플랫폼 지원
- 모든 플랫폼에서 공통 코드베이스 사용
- XCTest와 달리 플랫폼 간 일관된 동작 보장
오픈 소스
- GitHub에서 호스팅되는 오픈 소스 프로젝트
- 공개 기능 제안 프로세스를 통한 커뮤니티 참여
Swift Testing의 핵심 구성 요소
1. @Test - 테스트 함수
Swift Testing의 가장 기본적인 구성 요소는 @Test 속성을 가진 함수입니다.
@Test는 함수가 테스트임을 나타냅니다.
@Test를 추가하면 Xcode가 인식하고 위 사진처럼 옆에 Run 버튼을 표시합니다.
import Testing
@Test func videoMetadata() {
// 테스트 로직
}
@Test는 아래와 같은 특징이 있습니다.
- 전역 함수, 인스턴스 메서드, 정적 메서드 모두 지원
- 비동기 함수 및 예외 처리 함수 지원
- 전역 액터로 격리 가능
2. #expect, #require - 기대치
Swift Testing은 2가지 주요 매크로를 제공합니다.
#expect 매크로
@Test func videoMetadata() {
let video = Video(fileName: "By the Lake.mov")
let expectedMetadata = Metadata(duration: .seconds(90))
#expect(video.metadata == expectedMetadata)
}
#require 매크로
@Test func videoProcessing() async throws {
let videoLibrary = try await VideoLibrary()
let video = try #require(await videoLibrary.video(named: "By the Lake"))
#expect(video.formattedDuration == "0m 19s")
}
#expect는 중간에 테스트가 실패해도 나머지 테스트들을 계속 진행하고 #require은 중간에 테스트가 실패하면 테스트가 즉시 중단되며 오류가 발생합니다.
매크로의 장점으로는 일반 표현식과 언어 연산자 사용이 가능하며, 실패시 소스 코드와 하위 표현식의 값을 자동으로 캡처합니다.
실패를 하면 빨간색 X 아이콘이 표시되고 에러메세지를 클릭하고 Show를 선택해서 해당 줄에서 잘못된 부분을 자세히 확인할 수 있습니다.
#require 매크로를 사용할 수 있는 또 다른 방법은 선택적 값을 안전하게 언래핑하고 값이 nil인 경우 테스트를 중지하는 것입니다.
3. Traits - 특성
특성을 사용하여 테스트의 동작을 맞춤화할 수 있습니다.
예를 들어 테스트에 대한 설명 정보를 추가하거나 테스트 실행 시기 또는 실행 여부를 맞춤화하거나, 테스트 동작 방식을 수정할 수 있습니다.
표시 이름
@Test("Check video metadata") func videoMetadata() {
// 테스트 로직
}
버그 추적
@Test(.disabled("Due to a known crash"),
.bug("example.org/bugs/1234", "Program crashes at <symbol>"))
func example() {
// 버그 URL과 함께 비활성화
}
OS 버전 조건
@Test
@available(macOS 15, *)
func usesNewAPIs() {
// macOS 15 이상에서만 실행
}
4. Suite - 테스트 모음
Suite는 관련 테스트 함수 또는 다른 Suite 그룹화에 사용됩니다.
Suite은 @Suite 속성으로 명시적으로 주석을 달 수 있습니다 테스트 함수 또는 @Suites를 포함하는 모든 유형은 암시적으로 @Suite 자체로 간주합니다. Suite은 저장된 인스턴스 속성을 가질 수 있습니다 각 테스트 전후에 init 또는 deinit으로 로직을 수행할 수 있습니다 그리고 별도의 @Suite 인스턴스가 모든 인스턴스 @Test 함수에 대해 별도로 생성되어 의도치 않은 상태 공유를 방지합니다.
struct VideoTests {
let video = Video(fileName: "By the Lake.mov")
@Test("Check video metadata") func videoMetadata() {
let expectedMetadata = Metadata(duration: .seconds(90))
#expect(video.metadata == expectedMetadata)
}
@Test func rating() async throws {
#expect(video.contentRating == "G")
}
}
특징
- 구조체, 액터, 클래스 모두 사용 가능 (구조체 권장)
- 저장된 인스턴스 속성 지원
- init/deinit을 통한 setup/teardown 로직
- 각 테스트마다 별도의 인스턴스 생성으로 상태 분리
고급 기능
태그 시스템
관련 테스트들을 태그로 연결하여 관리할 수 있습니다.
@Suite(.tags(.formatting))
struct MetadataPresentation {
@Test func rating() async throws {
// 포맷팅 관련 테스트
}
@Test func formattedDuration() async throws {
// 포맷팅 관련 테스트
}
}
태그 활용
- 특정 기능이나 하위 시스템별 테스트 그룹화
- 테스트 계획에서 포함/제외 설정
- Test Report에서 필터링
- 크로스 프로젝트 공유 가능
매개변수화된 테스트
Swift Testing의 핵심 기능 중 하나인 매개변수화된 테스트를 통해 중복 코드를 크게 줄일 수 있습니다.
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)
}
// ... 더 많은 반복적인 테스트들
}
기존에는 이렇게 코드를 작성했었다면, 매개변수화된 테스트를 사용하면 아래처럼 코드를 작성할 수 있습니다.
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)
}
}
매개변수화된 테스트의 장점
- 각 인수별로 독립적인 결과 확인 가능
- 실패한 인수만 선택적으로 재실행 가능
- 병렬 실행을 통한 성능 향상
- 코드 중복 제거 및 유지보수성 향상
감사합니다.
잘못된 내용이 있거나 더 좋은 내용 피드백은 언제나 환영합니다!
궁금하신 부분은 댓글로 질문 부탁드립니다!
'Apple > Swift' 카테고리의 다른 글
[Swift] Multipeer Connectivity 톺아보기 (3) | 2025.08.23 |
---|---|
[Swift] suffix()로 인한 시간 초과 문제 해결하기 (8) | 2025.06.27 |
[Swift] Foundation Models Framework (6) | 2025.06.22 |
[Swift] Core ML과 MFCC를 활용한 감정 추론 (7) | 2025.05.14 |
[Swift] Tuist 살펴보기 (0) | 2025.04.27 |