테스트를 작성해본 적이 없다면, 테스트 타겟 추가부터 해야 한다.
테스트 작성
import Testing
@Test func videoMetadata() {
// ...
}
단정문 작성
원하는 조건이 참인지 확인한다.
일반적인 표현식과 연산을 받는다.
실패 했을 때 소스코드와 서브 표현식의 값을 캡쳐한다.
import Testing
@testable import DestinationVideo
@Test func videoMetadata() {
let video = Video(fileName: "By the Lake.mov")
let expectedMetadata = Metadata(duration: .seconds(90))
#expect(video.metadata == expectedMetadata)
}
expect 매크로 예시
#expect(1 == 2)
#expect(user.name == "Alice")
#expect(!array.isEmpty)
#expect(numbers.contains(4))
조건이 맞지 않으면 테스트를 빠르게 끝내고 싶을 때는 #require 매크로를 사용하라.
에러를 던진다.
try #require(session.isValid)
session.invalidate()
언래핑에도 쓸 수 있다.
let method = try #require(paymentMethods.first)
#expect(method.isDefault)
테스트에 커스텀 이름 붙이기
Test Navigator와 Xcode의 다른 곳에서 보여진다.
Trait의 예시
@Test("Check video metadata") func videoMetadata() {
let video = Video(fileName: "By the Lake.mov")
let expectedMetadata = Metadata(duration: .seconds(90))
#expect(video.metadata == expectedMetadata)
}
Traits
테스트에 대한 서술 정보 표현
테스트 실행 시점, 실행 여부 커스텀
테스트 동작 수정
테스트 그룹화
struct로 감싸면 된다. 이 타입이 Test Suite가 된다.
@Suite 매크로로 명시적으로 표현하는 것도 가능하다.
struct VideoTests {
@Test("Check video metadata") func videoMetadata() {
let video = Video(fileName: "By the Lake.mov")
let expectedMetadata = Metadata(duration: .seconds(90))
#expect(video.metadata == expectedMetadata)
}
@Test func rating() async throws {
let video = Video(fileName: "By the Lake.mov")
#expect(video.contentRating == "G")
}
}
상태를 가지는 것도 가능하다.
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 로직을 수행한다.
@Test 메소드마다 한개씩 초기화되어서 원하지 않는 상태 공유를 방지한다.
이 모든 것이 Swift를 위해서 디자인 되었다.
Test With Conditions
런타임에 평가 되어야 하는 조건을 명시해서 false면 테스트를 실행하지 않는다.
@Test(.enabled(if: AppFeatures.isCommentingEnabled))
func videoCommenting() {
// ...
}
아예 안 돌리려면 .disable trait을 사용한다.
@Test(.disabled("Due to a known crash"))
func example() {
// ...
}
버그 추적 시스템과 연결짓기
@Test(.disabled("Due to a known crash"),
.bug("example.org/bugs/1234", "Program crashes at <symbol>"))
func example() {
// ...
}
특정 버전에서만 테스트가 돌아야 하는 경우는 함수 자체에 @available을 붙여야 리포트에서 이를 반영해줄 수 있다.
// ❌ Avoid checking availability at runtime using #available
@Test func hasRuntimeVersionCheck() {
guard #available(macOS 15, *) else { return }
// ...
}
// ✅ Prefer @available attribute on test function
@Test
@available(macOS 15, *)
func usesNewAPIs() {
// ...
}
Tests with common characteristics
태그를 통해서 다른 Test Suite나 파일에 있더라도 연관지을 수 있다.
@Test(.tags(.formatting)) func rating() async throws {
#expect(video.contentRating == "G")
}
suite 단위로도 붙일 수 잇고, 이 경우는 해당 suite아래의 함수에 모두 같은 태그가 상속된다.
@Suite(.tags(.formatting))
struct MetadataPresentation {
let video = Video(fileName: "By the Lake.mov")
@Test func rating() async throws {
#expect(video.contentRating == "G")
}
@Test func formattedDuration() async throws {
let videoLibrary = try await VideoLibrary()
let video = try #require(await videoLibrary.video(named: "By the Lake"))
#expect(video.formattedDuration == "0m 19s")
}
}
특정 태그가 달린 테스트만 실행하거나, 테스트 리포트에서 특정 태그만 필터링할 수 있다.
테스트 포함/제거 여부를 결정할 때 테스트 이름으로 필터링하는 것보다 태그를 권장한다.
최적의 결과를 위해서는 최적의 trait을 사용할 것
Tests with different arguments
인자를 추가해야한다. 그러면 인자를 추가해야 한다고 에러가 난다.
인자를 Test 매크로에 추가하고, 매개변수를 사용하도록 테스트를 수정한다.
매개변수를 쓰더라도 여전히 이름이나 여러 trait들을 적용하는데는 문제가 없다. 매개변수 앞에다 써주면 된다.
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)
}
}
특정 매개변수만 따로 돌리는 것도 Test Navigator를 통해서 가능하다.