스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift Testing 소개
Swift Testing을 Swift로 작성된 코드를 테스트하기 위한 새로운 패키지입니다. 새롭고 강력한 API의 기본 구성 요소에 대해 알아보고, Swift Testing을 일반적인 테스트 작업 흐름에 적용할 수 있는 방법을 살펴보고, Swift Testing이 XCTest 및 오픈 소스 Swift와 어떤 관련이 있는지 확인해 보세요.
챕터
- 0:00 - Introduction
- 0:59 - Agenda
- 1:20 - Building blocks
- 1:58 - Building blocks: @Test functions
- 3:07 - Building blocks: Expectations (#expect and #require)
- 6:02 - Building blocks: Traits
- 6:49 - Building blocks: @Suite types
- 8:34 - Building blocks: Designed for Swift
- 9:14 - Common workflows
- 9:29 - Common workflows: Tests with conditions
- 10:56 - Common workflows: Tests with common characteristics
- 13:13 - Common workflows: Tests with different arguments
- 17:35 - Swift Testing and XCTest
- 21:52 - Open source
- 23:29 - Wrap up
리소스
- Adding tests to your Xcode project
- Forum: Developer Tools & Services
- Improving code assessment by organizing tests into test plans
- Running tests and interpreting results
- Swift Testing
- Swift Testing GitHub repository
- Swift Testing vision document
관련 비디오
WWDC24
-
다운로드
안녕하세요! 저는 Stuart Montgomery입니다 여러분께 Swift Testing을 소개해 드리겠습니다 뛰어난 사용자 경험을 제공하려면 품질과 안정성이 매우 중요합니다 자동화된 테스트는 시간이 지나도 소프트웨어 품질을 달성하고 유지할 수 있는 입증된 방법입니다 그래서 올해에는 그 어느 때보다 쉽고 강력하게 Swift 코드를 테스트할 수 있는 새로운 도구 세트를 소개합니다 Swift Testing은 새로운 오픈 소스 패키지로 Swift를 사용해 코드를 테스트할 수 있습니다 테스트를 설명하고 구성하는 강력한 기능이 포함되어 있으며 장애 발생 시 대처를 위한 세부 정보를 제공하고 대규모 코드베이스에 맞게 확장할 수 있습니다
Swift Testing은 Swift용으로 만들어져 동시성 및 매크로와 같은 최신 기능을 도입했습니다 Linux와 Windows 등 모든 주요 플랫폼을 지원합니다 또한 개방형 개발 프로세스로 여러분과 다른 커뮤니티가 다 함께 프로세스를 발전시킬 수 있는 기회를 제공합니다 이 세션에서는 먼저 Swift Testing의 구성 요소와 알아야 할 핵심 개념에 대해 이야기하겠습니다 그런 다음 일반적인 작업 흐름, 즉 테스트를 맞춤화하거나 다른 인수로 테스트를 반복하는 방법 등을 설명하겠습니다 Swift Testing과 XCTest가 어떻게 연관되어 있는지 살펴봅니다 그리고 마지막으로 오픈 소스 커뮤니티에서 이 새로운 프로젝트의 역할을 설명합니다 Swift Testing의 구성 요소를 살펴보면서 시작하겠습니다 앱에 대한 테스트를 작성해 본 적이 없다면 첫 번째 단계는 프로젝트에 테스트 번들 대상을 추가하는 것입니다 File > New > Target을 선택합니다
그런 다음 Test 섹션에서 Unit Testing Bundle을 검색합니다
Swift Testing은 이제 Xcode 16에서 이 템플릿에 대한 테스트 시스템의 기본 선택입니다 새 대상의 이름을 선택하고 Finish를 클릭하기만 하면 됩니다 이 앱에는 이미 테스트 대상이 있으니 첫 테스트를 작성해 보겠습니다 먼저 Testing 모듈을 임포트하겠습니다
그런 다음 전역 함수를 작성하고
여기에 @Test 속성을 추가하겠습니다
@Test 속성이 첫 번째 구성 요소입니다 함수가 테스트임을 나타내죠 이것을 추가하면 Xcode가 인식하고 옆에 Run 버튼을 표시합니다 테스트 함수는 @Test 속성을 가진 일반적인 Swift 함수입니다 전역 함수일 수도 있고 유형의 메서드일 수도 있습니다 비동기 또는 예외로 표시하거나 필요한 경우 전역 액터로 격리할 수 있습니다 다음으로 테스트가 실제로 무언가를 검증하도록 함수의 본문을 작성하겠습니다 이 테스트에서 확인할 것은 비디오 파일의 메타데이터가 예상과 일치하는지입니다 먼저 확인하려는 비디오와 예상되는 메타데이터를 초기화합니다
이러한 유형이 이 앱의 모듈로 선언되어 오류가 발생하므로 먼저 해당 모듈을 가져와야 합니다
이 가져오기에서는 소문자 @testable 속성을 사용하겠습니다
Swift Testing의 일부가 아닌 일반적인 언어 기능이지만 액세스 수준이 내부인 이러한 유형을 참고할 수 있게 합니다 다음으로 #expect 매크로를 사용해 비디오 메타데이터를 확인하겠습니다
#expect 매크로는 기대치를 수행하며 이것이 Swift Testing의 두 번째 구성 요소입니다
#expect 매크로와 같은 기대치를 사용하여 예상 조건이 true인지 확인할 수 있습니다 이 매크로는 일반 표현식과 언어 연산자를 허용합니다 또한 실패할 경우 소스 코드와 하위 표현식의 값을 캡처합니다 테스트를 처음으로 실행하고 어떻게 되는지 보겠습니다
실패한 것 같군요 빨간색 X 아이콘이 표시됩니다 테스트 실패 메시지를 클릭하고 Show를 선택하여 이 줄에서 잘못된 부분을 자세히 확인할 수 있습니다
이 결과 보기의 세부 정보에는 #expect 매크로에 전달된 표현식에 대한 세부 정보가 하위값과 함께 표시됩니다 메타데이터를 펼치면 해당 속성을 비교할 수 있습니다
기간과 해상도 필드가 모두 서로 같지 않은 것처럼 보입니다 이를 살펴보며 드는 생각이 Video 유형이 초기화된 후 메타데이터가 로드되지 않는 것 같습니다 이 문제는 분할 편집기에서 비디오 이니셜라이저로 이동하여 속성이 할당되었는지 확인하여 해결할 수 있습니다
테스트를 다시 실행해 보겠습니다
성공했네요! 좋습니다 #expect 매크로는 매우 유연합니다 연산자나 메서드 호출을 포함한 모든 표현식을 전달할 수 있으며 실패할 경우 자세한 결과를 보여 줍니다 몇 가지 예를 들겠습니다 == 연산자를 사용할 수 있으며 실패 시 왼쪽과 오른쪽이 캡처되어 표시됩니다 .isEmpty와 같은 속성에 액세스할 수 있고 배열에서 .contains와 같은 메서드를 호출할 수도 있습니다 오류를 잘 보시면 숫자 배열의 내용이 자동으로 표시되어 있습니다 이 모든 작업을 하기 위해 전문적인 API를 배우지 않아도 됩니다 #expect 매크로만 사용하면 됩니다
테스트에서 예상한 결과가 나오지 않아 조기 종료하고 싶을 때가 있습니다 이 경우 #require 매크로를 사용할 수 있습니다 요구되는 기대치는 일반적인 기대치와 유사합니다 하지만 try 키워드가 있고 표현식이 false이면 오류가 발생해 테스트가 실패하고 더 이상 진행되지 않습니다 #require 매크로를 사용할 수 있는 또 다른 방법은 선택적 값을 안전하게 언래핑하고 값이 nil인 경우 테스트를 중지하는 것입니다
이 예시에서는 #require 매크로를 사용하여 컬렉션의 .first 속성에 액세스하고 요소의 속성을 확인하는 방법을 보여 줍니다 ‘first’ 속성은 선택 사항이지만 테스트의 두 번째 줄은 해당 값에 의존하므로 이 테스트는 조기에 중지됩니다 래핑 해제된 값이 nil이면 계속하는 것이 합리적이지 않기 때문입니다 요구되는 기대치는 이 패턴을 위한 훌륭한 도구입니다
이 테스트를 프로젝트에 커밋하기 전에 목적을 더 명확히 해 봅시다 @Test 속성에 맞춤화된 표시 이름을 전달하면 됩니다 그러면 해당 이름이 테스트 내비게이터와 Xcode의 다른 위치에 표시됩니다
표시 이름은 세 번째 구성 요소인 특성의 예입니다
특성은 여러 가지를 할 수 있습니다 테스트에 대한 설명 정보를 추가하거나 테스트 실행 시기 또는 실행 여부를 맞춤화하거나 테스트 동작 방식을 수정할 수 있습니다
다음은 몇 가지 예시입니다 표시 이름과 함께 정보를 추가하는 것 외에도 관련 버그를 참고하거나 맞춤화 태그를 추가할 수도 있습니다 특정 조건에서만 테스트를 실행하려면 특성을 사용해 제어할 수 있습니다 일부 특성은 테스트의 실제 작동 방식에 영향을 줍니다 시간 제한을 설정하거나 한 번에 하나씩 실행하는 등으로요
첫 번째 테스트 작성을 마쳤으니 이제 두 번째 테스트 Video 유형의 다른 측면 검증 테스트입니다 이번에는 Xcode 16에 내장된 테스트 스니펫을 사용하여 빈 테스트 함수를 빠르게 추가해 보겠습니다
테스트 이름을 rating으로 하죠
그리고 본문에서는 이전과 같이 동영상을 생성하고 #expect로 contentRating이 기본값이 되도록 하겠습니다
이 두 테스트를 함께 그룹화하면 프로젝트에서 더 쉽게 찾을 수 있을 것입니다 그렇게 하려면 VideoTests라는 구조체로 래핑하면 됩니다
이렇게 하는 즉시 계층 구조가 테스트 내비게이터에 반영되며 이를 클릭하여 그룹으로 실행할 수도 있습니다 이렇게 테스트가 포함된 유형을 테스트 모음이라고 하며 이것이 네 번째이자 마지막 구성 요소입니다 모음은 관련 테스트 함수 또는 다른 모음 그룹화에 사용됩니다 모음은 @Suite 속성으로 명시적으로 주석을 달 수 있습니다 테스트 함수 또는 @Suites를 포함하는 모든 유형은 암시적으로 @Suite 자체로 간주하지만요 모음은 저장된 인스턴스 속성을 가질 수 있습니다 각 테스트 전후에 init 또는 deinit으로 로직을 수행할 수 있습니다 그리고 별도의 @Suite 인스턴스가 모든 인스턴스 @Test 함수에 대해 별도로 생성되어 의도치 않은 상태 공유를 방지합니다
이 두 테스트는 동일한 방식으로 시작됩니다 비디오 생성을 위한 코드의 첫 줄이 동일하기 때문입니다 테스트가 하나의 모음에 있으니 해당 줄을 다음과 같이 저장된 속성에 포함시켜 반복을 줄일 수 있습니다
이제 두 번째 테스트에서 해당 줄을 삭제할 수 있습니다
각 테스트 함수가 호출되는 곳은 포함된 집합 유형의 새 인스턴스이므로 각각 자체 비디오 인스턴스를 갖게 되며 실수로 상태를 공유할 수 없습니다
그럼 구성 요소를 복습해 보겠습니다 테스트 함수, 기대치 특성, 모음에 대해 설명드렸습니다 모두 여러 방법으로 Swift에서 편안하게 사용할 수 있게 설계되었습니다
테스트 함수는 비동기/대기 및 액터 격리를 지원하여 Swift 동시성과 원활하게 통합됩니다
기대치도 비동기/대기를 사용할 수 있으며 모든 내장된 언어 연산자를 사용할 수 있습니다
기대치와 특성 모두 Swift 매크로를 활용하므로 자세한 실패 결과를 볼 수 있으며 코드에서 직접 테스트별 정보를 지정할 수 있습니다
또한 모음은 값의 의미론을 채택해 상태를 분리하는 데 구조체 사용을 권장합니다
이제 이러한 구성 요소를 테스트의 일반적 문제에 적용하고 문제 해결을 위한 작업 흐름을 말씀드리겠습니다
테스트 실행 시기를 제어하고 공통점이 있는 테스트를 연결하고 매번 다른 인수로 테스트를 두 번 이상 반복하는 작업을 논의하겠습니다
먼저 조건이 있는 테스트입니다
일부 테스트는 특정 기기나 환경 등 특정 상황에서만 실행되어야 합니다 이러한 경우 조건 특성인 .enabled(if:...) 등을 적용할 수 있습니다
테스트가 실행되기 전에 평가할 조건을 전달하고 조건이 false이면 테스트가 건너뛴 것으로 표시됩니다 테스트가 절대 실행되지 않도록 하고 싶을 수도 있습니다 이 경우 .disabled(...) 특성을 사용할 수 있습니다 테스트 비활성화는 테스트 함수를 주석 처리하는 등의 다른 기법보다 선호되는데 컴파일된 테스트 내부의 코드를 확인하기 때문입니다
.disabled(...) 특성은 테스트가 비활성화된 이유를 설명하는 데 사용할 수 있는 주석을 허용합니다 그리고 주석은 항상 구조화된 결과에 표시되므로 CI 시스템에서 가시성을 위해 표시할 수 있습니다
테스트가 비활성화되는 이유는 버그 추적 시스템에서 추적되는 문제 때문인 경우가 많습니다 주석 외에도 .bug(...) 특성을 다른 특성과 함께 포함시켜 URL로 관련 이슈를 참고할 수 있습니다 그러면 Xcode 16의 테스트 보고서에서 해당 버그 특성을 확인하고 클릭해 해당 URL을 열 수 있습니다
테스트의 전체 본문을 특정 OS 버전에서만 실행할 수 있는 경우 해당 테스트에 @available(...) 속성을 배치하여 실행할 버전을 제어할 수 있습니다 런타임에서 확인할 때 #available을 사용하는 대신 @available(...) 속성을 사용하세요 @available(...) 속성을 사용하면 테스트 라이브러리가 테스트에 OS 버전 조건이 있다는 것을 알 수 있어 이를 결과에 더 정확하게 반영할 수 있습니다
다음으로 공통된 특징을 가진 테스트를 서로 다른 모음이나 파일에 있어도 연결시킬 수 있는 방법을 알아보겠습니다 Swift Testing은 테스트에 맞춤형 태그 할당 기능을 지원합니다 저도 이미 이 프로젝트에서 태그를 사용하기 시작했습니다 테스트 내비게이터 하단에 모든 태그가 표시됩니다
각 태그가 적용된 테스트를 보려면 새로운 Group By: Tag 모드로 전환할 수 있습니다
앞서 작성한 테스트 중 하나에 태그를 적용해 보겠습니다
이를 위해 @Test 속성을 통해 테스트에 태그 특성을 추가합니다
일부 데이터 서식 지정 로직의 유효성을 검사하는 테스트입니다 이 프로젝트에는 이미 서식 지정과 관련된 다른 테스트가 있으므로
이 테스트에도 서식 지정 태그를 추가해 보겠습니다
추가하고 나면 테스트 내비게이터의 해당 태그 아래에 표시됩니다
데이터 서식 지정 유효성을 검사하는 다른 테스트를 작성해 여기에 추가하겠습니다
이 두 테스트는 Video 정보의 서식 지정에 관한 것이므로 하위 모음으로 모아 보겠습니다
이제 서식 지정 태그를 @Suite로 위로 올려 여기 포함된 모든 테스트에서 상속되도록 할 수 있습니다
이제 태그가 상속되었으므로 각 개별 @Test 함수에서 태그를 삭제할 수 있습니다
공통점이 있는 테스트에 태그를 연결할 수 있습니다 예를 들어 특정한 기능이나 하위 시스템의 유효성을 검사하는 모든 테스트에 공통 태그를 적용할 수 있습니다 이렇게 하면 특정 태그가 있는 모든 테스트를 실행할 수 있습니다 또한 Test Report에서 필터링도 가능하며 동일한 태그를 가진 여러 테스트가 실패하기 시작하는 등의 경우에 대한 인사이트도 확인할 수 있습니다
태그 자체는 다른 파일, 모음 또는 대상의 테스트에 적용할 수 있으며 여러 프로젝트에서 공유할 수도 있습니다
Swift Testing을 사용할 때는 테스트 계획에 특정한 이름보다 태그를 포함시키거나 제외하는 것이 더 좋습니다
최상의 결과를 얻으려면 항상 각 상황에 가장 적합한 유형의 특성을 사용하세요 모든 시나리오가 태그를 사용할 필요는 없습니다 예를 들어 런타임 조건을 표현하려는 경우 앞서 언급한 대로 .enabled(if ...)를 사용하세요
Xcode의 테스트 태그 사용 방법을 자세히 알아보려면 ‘Swift Testing으로 테스트 심화하기’ 세션을 참고하세요
마지막으로 보여드릴 작업 흐름은 제가 가장 좋아하는 것입니다 매번 다른 인수를 사용하여 테스트를 반복하는 것입니다 이 방법이 유용한 이유를 보여주는 예시입니다 이 프로젝트에는 다양한 비디오에서 언급되는 대륙의 수를 확인하는 여러 테스트가 있습니다 각 테스트는 비슷한 패턴을 따르는데 새로운 videoLibrary를 생성하고
비디오를 이름으로 조회한 다음 #expect 매크로를 사용해 언급된 대륙 수를 확인합니다
이러한 테스트는 작동은 하지만 매우 반복적이며 테스트를 추가하는 비디오가 많아질수록 중복된 코드 때문에 유지 관리가 더 어려워집니다 또한 이 패턴을 사용하면 각 테스트에 고유한 함수 이름을 지정해야 하는데 이러한 이름은 읽기가 어렵고 테스트하는 비디오의 이름과 일치하지 않을 수 있습니다 대신 이 모든 것을 테스트 하나로 작성할 수 있습니다 매개변수화된 테스트라는 기능을 사용하면 됩니다 이 첫 번째 테스트를 매개변수화해 보겠습니다
첫 번째 단계는 서명에 매개변수를 추가하는 것입니다
이렇게 하자마자 오류가 나타납니다 이 테스트에 전달할 인수를 지정해야 하는군요 이를 수정해 보겠습니다
지금은 비디오 이름을 세 개만 포함시키겠습니다
저는 인수를 읽기 쉽도록 여러 줄로 나누는 방법을 선호하지만 형식은 원하는 대로 하시면 됩니다 마지막 단계는 조회할 비디오의 이름을 전달받은 인수로 대체하는 것입니다
이 테스트는 이제 여러 비디오를 다루게 되었으니 이름을 일반화해 보겠습니다
이제 이 테스트의 전체 이름에 매개변수 레이블이 포함됩니다
하지만 원하는 경우 인자 앞에 표시 이름이나 다른 특성을 전달하여 이름을 지정할 수 있습니다
이제 테스트를 실행하여 어떻게 되는지 보겠습니다
훌륭하네요! 테스트가 성공했고 테스트 내비게이터가 별도의 테스트인 것처럼 그 아래에 각 개별 비디오를 표시합니다 이 구조 덕분에 인수 추가와 테스트 범위 확장이 정말 쉽습니다 이 목록에 나머지 비디오를 모두 추가하고 새로운 비디오도 몇 개 추가해 보겠습니다
이 시점에서 이전 @Test 함수는 더 이상 필요하지 않으므로 삭제할 수 있습니다
테스트를 한 번 더 실행하여 아직도 통과하는지 보겠습니다
흠, 마지막에 추가한 새 비디오 중 하나가 지금 오류를 일으키는 것 같습니다 인수를 클릭하면 그에 대한 세부 정보와 실패한 기대치를 볼 수 있습니다
이 문제를 조사하려면 디버거로 다시 실행하는 것이 도움이 되겠지만 시간 절약을 위해 실패한 인수만 다시 실행하는 것이 좋겠습니다 이제 Xcode 16에서는 테스트 내비게이터에서 실행 버튼을 클릭해 개별 인수를 실행할 수 있습니다 하지만 그 전에 테스트 시작 부분에 중단점을 추가해 보겠습니다
이제 다시 실행해 보겠습니다
디버거에 표시된 videoName은 ‘Scotland Coast’이므로 이 테스트가 정확히 관심 있는 인수로 실행 중임을 알 수 있습니다 여기에서 디버깅을 계속 진행하여 실패 원인을 파악할 수 있습니다 매개변수화된 테스트는 개념적으로 for...in 루프를 사용하여 여러 번 반복되는 단일 테스트와 유사합니다 여기 예시를 보겠습니다 videoNames의 배열이 있으며 테스트를 수행하기 위해 반복됩니다 그러나 이렇게 for...in 루프를 사용하면 몇 가지 단점이 있습니다
매개변수화된 테스트를 사용하면 각 개별 인수의 세부 사항을 결과에서 명확하게 확인할 수 있습니다 세분화된 디버깅을 위해 인수를 독립적으로 재실행할 수 있습니다 또한 각 인수를 병렬로 실행하여 더 효율적으로 실행할 수 있으므로 결과를 더 빨리 얻을 수 있습니다
매개변수화된 테스트는 여기서 살펴본 것보다 훨씬 더 발전된 방식으로 사용하여 두 입력 세트의 모든 조합을 테스트하는 등의 작업이 가능합니다 자세한 내용은 ‘Swift Testing으로 테스트 심화하기’ 세션을 참고하세요
이 패턴을 사용하는 테스트가 보일 때마다 매개변수화된 테스트 함수로 변환하는 것이 가장 좋습니다 함수에 매개변수를 추가하고
for...in 루프를 제거한 다음
인수를 @Test 속성으로 위로 이동하기만 하면 다 된 겁니다!
다음으로 Swift Testing과 XCTest가 서로 연관되는 방식입니다 XCTest를 작성해 보신 적이 있다면 이 새로운 테스트 시스템이 어떻게 비교를 하는지 테스트를 마이그레이션하는지 궁금하실 것입니다 Swift Testing은 XCTest와 비슷한 부분이 있지만 알아두어야 할 몇 가지 중요한 차이점도 있습니다 앞서 살펴본 세 가지 구성 요소인 테스트 함수, 기대치 모음을 비교해 보겠습니다
XCTest의 테스트는 이름이 ‘test’로 시작하는 모든 메서드입니다 하지만 Swift Testing은 @Test 속성으로 명시적으로 표시하므로 모호함이 없습니다
Swift Testing은 더 많은 종류의 함수를 지원하므로 원하는 경우 유형에서 인스턴스 메서드를 사용할 수 있으며 정적 또는 전역 함수도 사용할 수 있습니다 XCTest와 달리 Swift Testing은 테스트별 또는 모음별로 정보를 지정할 수 있는 특성을 지원합니다
Swift Testing은 병렬화에 대한 접근 방식도 다릅니다 Swift 동시성을 사용해 프로세스 중에 실행되며 iPhone 및 Apple Watch와 같은 물리적 기기를 지원합니다
이 두 시스템 사이의 기대치는 매우 다릅니다 XCTest는 이 개념을 어설션이라고 하며 이를 나타내기 위해 XCTAssert로 시작하는 많은 함수를 사용합니다 Swift Testing은 다른 접근 방법 두 가지를 사용합니다 두 개의 기본 매크로 #expect와 #require입니다
많은 특수 함수가 필요하지 않고 그 대신 일반 표현식과 언어 연산자를 #expect 또는 #require에 전달할 수 있습니다
예를 들어 이중 등호를 사용하여 같음을 확인하거나 > 연산자를 사용하여 두 값을 비교할 수 있습니다
또한 반대 연산자를 사용하여 기대치를 무효화할 수도 있습니다
테스트 실패 발생 후 테스트 중단을 처리하는 방법도 다릅니다 XCTest에서는 continueAfterFailure 속성을 false로 지정한 다음 이후에 실패하는 어설션이 있으면 테스트가 중단됩니다
Swift Testing에서는 모든 기대치를 필수로 만들 수 있습니다 #require를 #expect 대신 사용하면 실패 시 오류가 발생합니다 이를 통해 테스트를 중단할 기대치를 선택할 수 있으며 테스트 진행에 따라 대체할 수도 있습니다
모음 유형의 경우 XCTest는 클래스만 지원하며 클래스는 XCTestCase에서 상속해야 합니다
Swift Testing에서는 구조체, 액터 또는 클래스를 사용할 수 있는데 구조체 사용을 추천합니다 값의 의미론을 사용하여 의도하지 않은 상태 공유로 인한 버그를 피할 수 있기 때문입니다
모음은 @Suite 속성을 사용해 명시적으로 표시할 수 있습니다 하지만 테스트 함수 또는 중첩된 모음을 포함하는 모든 유형에는 암시적입니다 표시 이름이나 다른 특성을 지정할 때만 필요합니다
각 테스트 실행 전 로직 수행을 위해 XCTest는 여러 setUp 메서드를 제공하지만 Swift Testing은 이를 위해 유형의 이니셜라이저를 사용합니다 비동기 또는 예외가 될 수 있습니다
각 테스트 후에 로직을 수행해야 하는 경우 디이니셜라이저를 포함할 수 있습니다 디이니셜라이저는 모음 유형이 액터 또는 클래스인 경우에만 사용할 수 있으며 모음에 구조체 대신 참고 유형을 사용하는 가장 일반적인 이유입니다
마지막으로 Swift Testing에서는 중첩된 유형을 통해 테스트를 하위 그룹으로 그룹화할 수 있습니다
XCTest와 Swift Testing 테스트는 단일 대상에 공존할 수 있으므로 마이그레이션하기로 했다면 점진적으로 진행할 수 있으며 먼저 새 대상을 만들 필요가 없습니다
구조가 유사한 여러 XCTest 메서드를 마이그레이션하는 경우 앞서 설명한 대로 매개변수화된 테스트 하나로 통합할 수 있습니다 테스트 메서드가 하나만 있는 XCTest 클래스의 경우 전역 @Test 함수로 마이그레이션하는 것을 고려하세요 그리고 테스트 이름을 정할 때 더 이상 ‘test’를 앞에 붙일 필요가 없습니다
XCTest를 계속 사용해야 하는 테스트가 있는데 XCUIApplication과 같은 UI 자동화 API나 XCTMetric과 같은 성능 테스트 API를 사용하는 테스트는 Swift Testing에서 지원되지 않습니다
또한 Objective-C로만 작성할 수 있는 모든 테스트에도 XCTest를 사용해야 합니다 하지만 다른 언어로 작성된 코드의 유효성을 검사하는 테스트를 Swift로 작성하는 데는 Swift Testing을 사용할 수 있습니다 마지막으로 XCTest 어설션 함수를 Swift Testing에서 호출하거나 또는 그 반대로 #expect 매크로를 XCTest에서 호출하지 마세요
‘Migrating a test from XCTest’ 문서를 확인해 보시기 바랍니다 어설션을 변환하고 비동기 대기 조건을 처리하는 방법 등 자세한 내용이 설명되어 있습니다
지금까지 Swift Testing의 기능을 살펴보고 몇 가지 사용법도 보여드렸습니다 이것은 이 새로운 패키지의 시작에 불과하며 앞으로 커뮤니티에서 어떻게 발전할지 기대가 됩니다 Swift Testing은 오픈 소스이며 GitHub에서 호스팅됩니다 조만간 최근 발표된 swiftlang 조직으로 이동할 예정입니다 Swift 동시성을 지원하는 모든 Apple 운영 체제뿐만 아니라 Linux와 Windows에서도 작동합니다 그리고 중요한 것은 이 모든 플랫폼에서 공통 코드베이스를 사용한다는 것입니다! 이는 여러 구현이 있었던 XCTest에 비해 크게 개선된 점입니다 이는 플랫폼 사이를 이동할 때 테스트가 훨씬 더 일관적으로 동작하고 플랫폼 간 기능적 동등성이 향상된다는 의미입니다
Swift Testing은 Swift 에코시스템의 주요 도구 및 IDE에 통합되어 있습니다 커맨드 라인의 Swift Package Manager뿐만 아니라 최신 버전의 Swift 확장이 포함된 Xcode 16 및 VS Code 등도 마찬가지입니다
Swift Testing의 커맨드 라인 환경을 살펴보겠습니다 제가 만든 간단한 패키지입니다 Xcode 16에 포함된 새 패키지 템플릿으로 만들었죠 터미널에서 swift test를 입력하여 이 패키지를 테스트할 수 있습니다
그러면 XCTest 테스트와 Swift Testing이 모두 실행됩니다 콘솔은 다채로운 출력으로 pass 및 fail 결과를 표시하고 Xcode에 표시되는 것과 유사한 자세한 실패 메시지를 포함합니다 Swift Testing에는 공개 기능 제안 프로세스가 있으며 Swift Forum에서 그 발전에 대해 논의하고 있습니다 기능 제안을 작성하거나 제안에 참여하여 문서를 개선하거나 버그 리포트를 제출하실 수 있습니다 어떤 기여든 환영합니다! 지금까지 Swift Testing이었습니다 기대치 및 매개변수화된 테스트와 같은 강력한 기능을 사용하여 코드의 품질을 개선하고 특성을 사용하여 테스트를 맞춤화하고 GitHub와 Forum에서 저희와 함께 이 패키지의 미래를 만들어 보세요 ‘Swift Testing으로 테스트 심화하기’ 세션에서 테스트를 개선할 수 있는 더 많은 방법도 알아 보세요 시청해 주셔서 정말 감사합니다!
-
-
1:54 - Write your first @Test function
import Testing @Test func videoMetadata() { // ... }
-
2:35 - Validate an expected condition using #expect
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) }
-
4:24 - Fix a bug in the code being tested
// In `Video.init(...)` self.metadata = Metadata(forContentsOfUrl: url)
-
6:06 - Add a display name to a @Test function
@Test("Check video metadata") func videoMetadata() { let video = Video(fileName: "By the Lake.mov") let expectedMetadata = Metadata(duration: .seconds(90)) #expect(video.metadata == expectedMetadata) }
-
6:58 - Add a second @Test function
@Test func rating() async throws { let video = Video(fileName: "By the Lake.mov") #expect(video.contentRating == "G") }
-
7:18 - Organize @Test functions into a suite type
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") } }
-
8:04 - Factor a common value into a stored property in the suite
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") } }
-
9:32 - Specify a runtime condition trait for a @Test function
@Test(.enabled(if: AppFeatures.isCommentingEnabled)) func videoCommenting() { // ... }
-
9:49 - Unconditionally disable a @Test function
@Test(.disabled("Due to a known crash")) func example() { // ... }
-
10:15 - Include a bug trait with a URL along with other traits
@Test(.disabled("Due to a known crash"), .bug("example.org/bugs/1234", "Program crashes at <symbol>")) func example() { // ... }
-
10:33 - Conditionalize a test based on OS version
@Test @available(macOS 15, *) func usesNewAPIs() { // ... }
-
10:42 - Prefer @available on @Test function instead of #available within the function
// ❌ 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() { // ... }
-
11:22 - Add a tag to a @Test function
@Test(.tags(.formatting)) func rating() async throws { #expect(video.contentRating == "G") }
-
11:48 - Add another data formatting-related test with the same tag
@Test(.tags(.formatting)) 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") }
-
11:56 - Group related tests into a sub-suite
struct MetadataPresentation { let video = Video(fileName: "By the Lake.mov") @Test(.tags(.formatting)) func rating() async throws { #expect(video.contentRating == "G") } @Test(.tags(.formatting)) 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") } }
-
12:05 - Move common tags trait to @Suite attribute, so the suite's @Test functions will inherit the tag
@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") } }
-
13:27 - Example of some repetitive tests which can be consolidated into a parameterized @Test function
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 }
-
14:07 - Refactor several similar tests into a parameterized @Test function
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) } }
-
// Using a for…in loop to repeat a test (not recommended) @Test func mentionedContinentCounts() async throws { let videoNames = [ "A Beach", "By the Lake", "Camping in the Woods", ] let videoLibrary = try await VideoLibrary() for videoName in videoNames { let video = try #require(await videoLibrary.video(named: videoName)) #expect(!video.mentionedContinents.isEmpty) #expect(video.mentionedContinents.count <= 3) } }
-
17:15 - Refactor a test using a for…in loop into a parameterized @Test function
@Test(arguments: [ "A Beach", "By the Lake", "Camping in the Woods", ]) 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) }
-
22:47 - A newly-created Swift package with two simple @Test functions
import Testing @testable import MyLibrary @Test func example() throws { #expect("abc" == "abc") } @Test func failingExample() throws { #expect(123 == 456) }
-
22:56 - Running all tests for the package from Terminal
> swift test
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.