-
AppIntentsTesting으로 앱 인텐트 도입 검증하기
Siri, 단축어, Spotlight에서 사용하는 것과 동일한 인프라를 통해 앱 인텐트를 검증하기 위한 새로운 프레임워크인 AppIntentsTesting을 만나 보세요. UI 자동화 없이도 인텐트를 실행하고, 결과를 검사하며, 엔티티와 쿼리를 테스트하는 방법을 살펴보세요. 뷰 주석 및 Spotlight 인덱싱과 같은 통합을 확인하여 개발 워크플로 초기에 버그를 발견하는 방법을 알아보세요.
챕터
- 0:16 - Introduction
- 2:01 - Meet CometCal: your first test
- 2:29 - How AppIntentsTesting works
- 9:39 - Testing entity queries
- 13:49 - Combining multiple intents
- 16:27 - Test-only intents
- 18:22 - Testing Spotlight indexing
- 20:56 - Testing view annotations
- 24:00 - The App Intents testing workflow
- 25:19 - Next steps
리소스
-
비디오 검색…
안녕하세요. 저는 Venkatesh이고, App Intents 팀의 소프트웨어 엔지니어입니다. 오늘은 App Intents를 테스트하는 방법을
AppIntentsTesting이라는 새로운 프레임워크로 어떻게 할 수 있는지 알려드리겠습니다. App Intents 프레임워크는 핵심 요소로서 여러분의 앱이 시스템과 통합되는 방식을 담당합니다. 그리고 매년 더 많은 경험을 지원하고 있습니다. 예를 들어, App Intents를 통해 사용자들은 Siri로 앱과 상호작용할 수 있습니다. 단축어 앱을 사용해 강력한 자동화를 구성할 수도 있습니다.
Spotlight은 이를 활용해 앱의 콘텐츠를 시스템 전체 검색에 노출합니다. 위젯은 이를 사용해 홈 화면에 데이터를 표시합니다.
App Intents가 처음이라면 WWDC25의 이 강연을 확인해 보세요. 새로운 발전 사항을 알고 싶다면 WWDC26의 이 강연을 확인해 보세요. App Intents가 수많은 경험을 지원하는 만큼, 모든 intent, entity, query가 올바르게 동작해야 합니다. AppIntentsTesting은 완전히 새로운 프레임워크로 이 모든 것과 그 이상을 테스트할 수 있게 해줍니다. 오늘 다룰 내용은 다음과 같습니다. 먼저 샘플 앱을 소개하고, 첫 번째 테스트를 작성하는 방법을 보여드리겠습니다. 다음으로, AppIntentsTesting이 테스트를 실행하는 방식을 살펴보겠습니다.
그런 다음 한 단계 더 나아가 entity query를 테스트하고, 여러 intent를 결합하는 방법을 알아보겠습니다.
마지막으로, 앱을 넘어 범위가 확장되는 App Intents의 일부를 테스트해 보겠습니다. Spotlight과 뷰 어노테이션입니다.
이제 바로 들어가서 AppIntentsTesting을 얼마나 쉽게 시작할 수 있는지 보여드리겠습니다.
이것은 CometCal, 샘플 캘린더 앱입니다. 캘린더, 이벤트를 관리하는 SwiftUI 앱으로, 참석자를 관리하며 App Intents와 완전히 통합되어 있습니다. 동료 Justin과 저는 이 앱을 개발해 왔습니다. 우주 탐험대원들이 임무를 더 쉽게 관리할 수 있도록 하기 위해서입니다. 이번 세션에서는 이 앱을 테스트하는 방법을 AppIntentsTesting을 사용하여 알아보겠습니다.
CometCal을 Siri에서 사용 가능하게 만든 방법에 대해 알아보려면 Justin의 코드 연습 세션을 확인해 보세요.
먼저 Create Calendar 흐름을 실행하는 간단한 테스트를 작성해 보겠습니다.
AppIntentsTesting을 시작하는 데 필요한 것은 UI 테스팅 번들뿐입니다. 아직 없으니 하나 만들어 보겠습니다.
설명적인 이름을 지정해 보겠습니다. 다음으로 개발 팀을 선택합니다. AppIntentsTesting을 사용하려면 테스트 러너와 앱이 코드 서명을 위해 동일한 개발 팀을 사용해야 하므로 여기서 올바른 팀을 선택해야 합니다. 다음으로 Bundle Identifier를 입력합니다.
올바른 Target을 선택합니다. Finish를 클릭합니다.
이렇게 UI 테스팅 번들이 만들어집니다. intent 실행과 관련된 모든 테스트를 위한 새 파일을 만들어 보겠습니다.
이제 테스트를 작성할 준비가 되었습니다.
테스트할 intent로 이동해 보겠습니다.
CreateCalendar는 이름과 색상으로 새 캘린더를 생성합니다.
시작하려면 AppIntentsTesting 프레임워크를 import해야 합니다.
표준 XCTestCase로 시작하고 테스트 메서드를 만듭니다.
IntentDefinitions 객체를 생성하여 테스트 흐름을 시작합니다.
IntentDefinitions는 CometCal의 bundle identifier를 받아서 앱을 import하지 않고도 모든 intent, entity, 앱이 정의한 query에 접근할 수 있게 해줍니다. 특정 intent의 정의에 접근하려면 intents 프로퍼티를 사용하여 Intent 이름을 서브스크립트로 전달하면 됩니다.
이렇게 하면 IntentDefinition 객체가 반환됩니다. 그 위에 makeIntent를 호출하여 Intent의 채워진 인스턴스를 생성할 수 있습니다.
intent가 기대하는 매개변수를 전달할 수 있습니다.
여기서 CreateCalendarIntent는 이름과 색상을 받습니다. 저는 인류를 위한 중요한 임무를 맡고 있으며, 대원들과 함께 임무를 완수할 수 있도록 설명적인 이름이 필요합니다.
'Occupy Saturn'이라고 부르겠습니다. 그리고 색상을 지정합니다. 빨간색으로 만들겠습니다.
여기서 color는 AppEnum입니다. AppEnum의 raw string 값만 전달하면 프레임워크가 올바른 타입으로 변환합니다. 테스트 코드는 앱에 대해 빌드되지 않으므로, 매개변수 이름과 타입에 대한 자동 완성이 제공되지 않습니다. intent 매개변수를 기반으로 올바르게 입력해야 합니다. 이 타입 변환은 대부분의 매개변수 타입에서 기본으로 동작합니다. 그러나 사용자 정의 타입을 전달하고 싶다면, 문서에서 IntentValueConvertibleWrapper를 찾아보세요. intent를 생성했으니, .run() 메서드를 호출하여 앱에서 실행합니다.
intent의 perform 메서드에서 반환값이 필요하다면, run 메서드의 결과를 캡처하여 value 프로퍼티로 가져올 수 있습니다.
여기서 CreateCalendarIntent는 CalendarEntity로 새 캘린더를 반환합니다.
이것이 CalendarEntity입니다.
확인할 수 있는 title 프로퍼티가 있습니다.
새 이벤트에 올바른 title이 있는지 확인하고 싶다면,
먼저 result.value로 새 캘린더에 접근한 다음, 필요한 프로퍼티 이름을 체이닝하면 됩니다. 이는 동적 멤버 조회를 사용하여 새 캘린더에서 title을 가져옵니다.
이제 이 테스트를 실행할 준비가 되었습니다. 그 전에 휴대폰으로 전환하여 현재 어떤 캘린더가 있는지 확인해 보겠습니다.
앱에는 현재 세 개의 캘린더가 있습니다. Deep Space, Mission Control, Stargazing입니다. 테스트를 실행한 후, Occupy Saturn 캘린더가 목록에 추가되길 기대합니다.
Xcode로 돌아가 이 테스트를 실행해 보겠습니다.
테스트가 성공했습니다.
휴대폰에서 새 캘린더가 존재하는지 확인해 보겠습니다. 네, 테스트가 Occupy Saturn 캘린더를 빨간색으로 생성했습니다.
단 다섯 줄의 코드만으로 테스트를 사용하여 기기에서 App Intent를 실행했습니다. 이제 AppIntentsTesting을 실제로 보셨으니, 더 자세히 설명해 드리겠습니다.
AppIntentsTesting은 App Intents를 위한 통합 테스팅 프레임워크입니다. UI 테스트와 마찬가지로, 문자열을 사용하여 intent를 찾으며 앱의 내부에는 접근하지 않습니다.
테스트는 표준 XCUITest 번들 안에 위치하며, 자체 프로세스에서 실행됩니다. 앱은 별도의 프로세스에서 실행되며 기기에서 App Intents를 실행합니다.
테스트 러너는 프로세스 경계를 넘어 App Intents를 실행하고, 상태를 공유하지 않고 실행 결과를 받습니다.
개발자로서 AppIntentsTesting이 의미하는 바는 다음과 같습니다. 테스트는 전체 App Intents 스택을 통해 실행되며, 사용자들이 실제로 거치는 코드 경로와 동일합니다. 목(mock)도, 스텁(stub)도 없습니다.
XCUITest 번들을 새로 만들거나, 기존 XCUI 테스트에 추가할 수 있습니다. 지속적 통합(CI) 파이프라인이 자동으로 이를 인식합니다. 앱 코드 구조는 원하는 방식으로 구성하세요. 테스트 타겟은 애플리케이션의 코드를 전혀 import하지 않으며, bundle identifier만 전달합니다.
즉, 앱 코드를 테스팅 타겟에 컴파일할 필요가 없습니다.
이 모든 것이 릴리스 전반에 걸쳐 안정적인 테스트를 제공합니다. 어떤 UI에도 의존하지 않습니다. 앱의 UI도, 시스템의 UI도 아닙니다.
개요를 살펴봤으니, 이제 더 복잡한 테스트로 넘어가겠습니다. 앱의 액션, 데이터, query는
App Intents 통합의 기본 구성 요소입니다. 단축어, Siri 상호작용, Spotlight 검색을 지원합니다. 이 코드에 회귀가 발생하면, 사용자들이 예기치 않은 시스템 동작을 경험하는 경우가 많습니다.
entity가 조회될 때마다, 단축어에서, Siri를 통해, 혹은 App Intents가 노출하는 다른 어떤 곳에서든, entity query는 올바른 결과를 반환해야 합니다. AppIntentsTesting은 문자열 query, identifier 조회, 제안 entity에 대한 동작을 검증하는 데 도움을 줍니다. 상황을 설명하겠습니다. 저는 우주 광선이라는 대체로 무해한 장치를 가지고 있습니다. 불행한 사고로 인해, Cosmic Ray Calibration 이벤트의 이름을 Cosmic Ray Rebuilding으로 변경해야 합니다. 사실, 이런 일이 너무 자주 발생해서 단축어를 만들어야 할 것 같습니다.
이벤트는 이미 앱에 존재합니다. 그래서 단축어 앱으로 이동하여, 단축어로 가서 이벤트를 검색합니다.
처음에는 제안 이벤트 목록이 표시됩니다. 다른 이벤트를 검색하고 싶다면, 검색 창을 사용하면 EventEntity의 string query를 호출하여 결과를 결정합니다.
'사용 가능한 옵션 없음'; string query가 아직 구현되지 않았습니다. 테스트 주도 방식으로 AppIntentsTesting을 사용해 구현하겠습니다.
테스트는 다음과 같습니다. entity와 intent 정의를 생성하는 것부터 시작합니다.
앱에 알려진 이벤트 집합이 있는지 확인하기 위해, SeedSampleEventsIntent를 실행합니다. 이는 앱 데이터를 초기화하고 이벤트를 추가합니다. 이 부분은 잠시 후 다시 설명하겠습니다. 이상적으로는 Entity와 Intent 정의를 테스트 함수 밖으로 빼서 다른 테스트에서도 재사용하고, 데이터 시딩은 setUp 메서드로 옮기는 게 좋습니다. 그렇게 해보겠습니다. 이제부터는 테스트 기능에만 집중하겠습니다.
업데이트된 테스트입니다. Event entity definition에서 entities(matching:) 메서드를 사용하여 Entity string query를 호출합니다. 이는 기기에서 string query를 실행합니다. 이 메서드는 EventEntity 표현의 배열을 반환하므로, 개수를 확인하는 assert를 작성할 수 있습니다.
intent와 마찬가지로, 동적 멤버 조회를 사용하여 entity 프로퍼티의 값을 가져올 수 있습니다. 여기서는 이벤트의 title을 가져와서, query가 올바르게 동작하는지 확인하는 assert를 작성합니다. 테스트를 실행해 보겠습니다. 예상대로 실패합니다. 실패 메시지가 무엇을 만들어야 할지 정확하게 알려줍니다.
이제 EventEntityQuery로 이동합니다. 현재는 identifier로 이벤트를 반환하고, 제안 이벤트를 반환할 수 있습니다. 하지만 EntityStringQuery 기능이 없습니다. 추가해 보겠습니다.
EntityStringQuery 준수를 추가하고,
entities(matching:) 메서드를 구현합니다. 제목으로 이벤트를 필터링하고 일치하는 EventEntity를 반환합니다.
테스트로 돌아가 실행하면, 통과합니다. Entity string query가 예상대로 동작함을 확인했습니다.
이제 기기에서 변경 사항을 검증합니다. 먼저 이벤트가 존재하는지 확인합니다. 다음으로 단축어 앱으로 이동하여, 단축어를 열고 같은 이벤트를 다시 검색합니다. 이번에는 이벤트를 찾습니다.
먼저 테스트를 작성하고, 통과시키기 위해 기능을 구현했습니다. 이것이 AppIntentsTesting을 활용한 테스트 주도 개발입니다. 이제 그 우주 광선을 다시 만들 시간입니다! 한 단계 더 나아가 하나의 테스트에서 여러 intent를 결합해 봅시다.
복잡한 단축어를 만들 때는, intent의 결과를 가져다가 다른 intent의 매개변수로 전달하는 경우가 많습니다. 이 흐름은 많은 유용한 자동화의 기초를 형성합니다.
상황은 이렇습니다. 팀의 소행성 피하기 게임 연습 세션을 잡아놓았습니다. 그런데 팀원 중 한 명이 신입이어서, 이벤트 목적을 규칙 설명으로 변경하게 되었습니다. 이것이 앱의 핵심 워크플로우이므로, 시뮬레이션하는 테스트를 작성하겠습니다.
이 테스트는 이전 예제를 기반으로 합니다. 단계별로 설명해 드리겠습니다.
필요한 매개변수로 CreateEventIntent를 실행하는 것으로 시작합니다. 이전 예제에서 makeIntent가 타입 변환을 처리하는 방식을 설명했습니다.
처음 세 매개변수는 모두 원시 타입을 받습니다.
마지막 매개변수가 더 흥미롭습니다. calendar 매개변수는 CalendarEntity를 받습니다. 하지만 여기서는 문자열만 전달합니다. App Intents 런타임이 자동으로 EntityStringQuery를 호출하여 CalendarEntity와 연결된 첫 번째 일치하는 값을 채워 넣습니다.
이벤트를 생성한 후 title을 확인하여 올바른지 검증합니다.
다음으로 UpdateEventIntent를 실행합니다. 이 intent는 이벤트와 적용할 업데이트를 받습니다. title만 업데이트하므로 해당 값을 전달합니다. 업데이트할 이벤트의 경우, 원래 run 메서드에서 가져온 EventEntity를 직접 전달할 수 있습니다. 이는 사용자들이 단축어에서 intent를 구성하는 방식과 동일합니다.
마지막으로, 이벤트에 업데이트된 title이 있는지 확인합니다.
이번에는 테스트 설정 중에 앱을 실행하여, 올바르게 동작하는지 직접 확인합니다. 테스트를 실행할 시간입니다. 앱이 실행되고,
이벤트가 생성되고,
그런 다음 업데이트됩니다.
완료! Entity 생성, 체이닝, 업데이트, 그리고 assertion까지, 모두 기기에서 단일 테스트로 완료됩니다. 이제 소행성 피하기 게임 규칙서를 어디 두었더라… AppIntentsTesting 테스트는 경량이고 빠르며 모든 커밋에서 실행할 수 있습니다.
테스트 결과가 신뢰할 수 있으려면 각 테스트가 독립적으로 완결되어야 합니다. 테스트 전용 intent가 이를 도와줍니다.
테스트 전용 intent는 단순하고, 테스트를 돕기 위해서만 존재하는 집중된 intent입니다. 테스트 기능을 개선하는 데 사용할 수 있습니다. 테스트에 필요한 데이터를 정확하게 생성할 수 있습니다. 이전 실행에서 남겨진 데이터가 불안정한 결과를 유발하지 않습니다.
UI 탐색 없이 앱의 어떤 뷰로든 바로 이동할 수 있습니다. 화면을 재설계하더라도 이 테스트는 계속 작동합니다. 더 큰 그림은 이렇습니다. 테스트 전용 intent는 앱의 모든 기능을 감쌀 수 있으며, 아직 App Intents를 적용하지 않은 기능도 포함됩니다. 내부 탐색, 데이터 관리, 상태 조작 등을 테스트 전용 intent로 감싸면, AppIntentsTesting을 통해 테스트할 수 있습니다. CometCal의 소스 코드에는 여러 테스트 전용 intent가 포함되어 있습니다. 앞서 string query 테스트에서 사용한 SeedSampleEventsIntent가 그 예입니다. 캘린더에 알려진 이벤트 집합을 생성합니다. 어떤 intent든 테스트 전용 intent로 만드는 방법입니다.
isDiscoverable: false로 표시합니다. 이렇게 하면 시스템이 다른 곳에 노출하지 않게 됩니다.
그리고 #if DEBUG 컴파일러 지시문으로 감싸서 테스트만 이 intent에 접근할 수 있도록 합니다.
지금까지 App Intents를 독립적으로 테스트하는 방법을 배웠습니다. 하지만 진정한 강점은 App Intents가 시스템 전체에 걸쳐 사용자에게 기능을 제공하는 방식에 있습니다. 앱 밖에서도 앱의 기능을 사용할 수 있게 해줍니다. 이제 그러한 시스템 수준 통합을 테스트하는 방법을 보여드리겠습니다.
Spotlight을 통해 사람들은 한 곳에서 전체 시스템을 검색할 수 있습니다. entity를 인덱싱하면 해당 결과에 바로 표시되어, 사용자가 앱을 먼저 열지 않고도 앱의 콘텐츠를 찾을 수 있습니다. CometCal이 이벤트를 생성할 때 Spotlight에 entity를 인덱싱해야 합니다. 그런데 한번은 함대 사령관으로부터 긴급 메시지를 받았는데, Dark Matter Symposium이 언제인지 물어보는 내용이었습니다.
나노초도 소중하다는 것을 알기에, Spotlight을 사용해 이 정보를 빠르게 찾아 답장하려 했습니다. 하지만 Spotlight 검색 결과에는 아무것도 표시되지 않았습니다. Dark Matter에 관한 정보가 무척 흥미롭긴 하지만, 제가 찾던 것이 아닙니다! 앱에 들어가 보니 이벤트는 실제로 존재했습니다. 빠르게 세부 정보를 전송하고 디버깅을 시작했습니다.
디버깅을 위해 이벤트 생성 코드로 이동했습니다. 버그는 간단했습니다. 개발 중에 인덱싱 코드를 주석 처리했다가 다시 활성화하지 않은 것이었습니다. 앱의 동작이 변하지 않아서 버그가 감지되지 않았습니다.
수정 방법도 간단합니다. 인덱싱 호출의 주석을 해제하면 됩니다.
하지만 이런 일이 다시 발생하지 않으려면 어떻게 해야 할까요? 버그를 찾기 위해서가 아니라 회귀를 방지하기 위한 테스트를 작성할 수 있습니다.
spotlightQuery() 메서드를 사용하여 Spotlight와 연동하는 것으로 시작합니다. 이 메서드는 문자열을 받아, 문자열과 일치하는 Spotlight에 인덱싱된 EventEntity 표현 목록을 반환합니다. 처음에는 이벤트가 존재하지 않으므로, spotlightQuery()가 결과를 반환하지 않는지 확인합니다.
다음으로, CreateEventIntent를 사용하여 이벤트를 생성합니다. 이전과 완전히 동일한 방식입니다.
생성 후, 이벤트가 Spotlight 인덱스에 존재하길 기대하므로, 다시 query합니다. 결과가 정확히 하나이고 title이 일치하는지 확인합니다. 이제 테스트를 실행하면… 통과합니다. 이 테스트는 이제 모든 커밋에서 실행됩니다. 따라서 Spotlight 통합이 다시 깨진다면, 이 테스트가 즉시 이를 잡아냅니다.
이제 이벤트가 실제로 Spotlight에 인덱싱되었는지 빠르게 확인해 보겠습니다. 확인! 이제 함대 사령관은 Spotlight 속도로 답변을 받을 수 있습니다!
뷰 어노테이션은 현재 뷰에 어떤 entity가 표시되고 있는지 시스템에 알려주므로, Siri가 화면에 표시된 내용을 이해하고 직접 조치를 취할 수 있습니다. CometCal은 이벤트로 이동할 때마다 화면에 표시된 EventEntity를 Siri에게 알립니다. 이벤트를 보고 있을 때, 예기치 않은 웜홀 점프로 손에서 폰이 날아가더라도, "Siri, 이 이벤트가 몇 시야?"라고 언제든 말할 수 있습니다.
이상적으로는 뷰 어노테이션이 화면에 어떤 이벤트가 있는지 Siri에게 정확히 알려주어, Siri가 바로 답변해야 합니다. 하지만 어노테이션이 깨진다면, Siri는 화면에 무엇이 있는지 알 수 없습니다.
차원 간 이동이 제가 인정하고 싶은 것보다 더 자주 일어나서, 이 기능이 반드시 작동해야 합니다. AppIntentsTesting의 도움으로 버그를 수정해 보겠습니다.
먼저 OpenEventIntent를 사용하여 이벤트 페이지로 이동합니다.
이 테스트가 XCUITest 번들에 있기 때문에, XCUI 자동화를 사용하여 앱이 화면에 올바른 이벤트를 실제로 렌더링하는지 확인할 수 있습니다.
그런 다음 Event entity에서 viewAnnotations() 메서드를 호출합니다. 이 메서드는 시스템이 현재 화면에 있다고 보고하는 EventEntity 뷰 어노테이션 목록을 가져옵니다. 시스템이 현재 화면에 있다고 보고합니다.
이제 assertion을 작성합니다. 먼저, 화면에 하나의 이벤트만 있으므로, 시스템이 하나의 ViewAnnotation 객체만 반환하는지 확인합니다.
그런 다음 title을 기반으로 Event entity가 올바른지 확인합니다.
ViewAnnotation 객체에는 entity 프로퍼티가 있으며, 실제 entity 표현을 담고 있습니다. 따라서 동적 멤버 조회를 사용하여 title과 같은 특정 프로퍼티에 대한 assert를 작성할 수 있습니다. 이제 테스트를 실행합니다. 실패합니다.
그런 다음 EventDetailView로 이동하여 이유를 찾아봅니다. 문제는 EntityIdentifier에 있습니다. 실수로 이벤트의 calendar id를 identifier로 전달한 것입니다.
수정 방법은 간단합니다. 이벤트의 id로 변경합니다.
이제 테스트를 다시 실행합니다. 이번에는 통과합니다.
이제 다음번에 차원 간 이동이 갑자기 일어나더라도, "Siri, 이 이벤트가 언제야?"라고 말하면,
올바른 응답을 받을 수 있습니다. "어디서 해?"라고 이어서 물어보면,
Siri가 장소를 알려줍니다.
오늘 많은 내용을 다뤘습니다. 이제 한 걸음 물러서서 AppIntentsTesting이 어떻게 App Intents 개발 워크플로우를 보강할 수 있는지 살펴보겠습니다.
기본 타입을 구현하여 App Intents 여정을 시작하세요. 앱의 액션, 데이터, query가 이에 해당합니다. 이제 AppIntentsTesting으로 이러한 타입의 기본 동작을 검증할 수 있습니다.
이 테스트들은 App Intents 통합을 위한 단위 테스트 역할을 합니다.
기본 사항을 테스트한 후에는, 앱을 시스템과 더 깊이 통합하세요. 뷰에 entity를 어노테이션하고, Spotlight에 기부하고, 앱 간에 데이터를 전달합니다. AppIntentsTesting을 통해 이러한 통합도 커버하여, 앱의 경험을 사람들이 사용하는 다양한 방식으로 검증합니다.
이것이 App Intents 통합 테스트가 될 수 있습니다. 모든 레이어에서 커버리지를 확보하면, 앱은 준비됩니다. 진정으로 강력한 경험을 제공할 준비가 됩니다. 이 단계에서는 Siri와 단축어 앱으로 intent를 직접 테스트하여 실제 사용자가 경험하는 것처럼 직접 경험해 보세요. 이것이 AppIntentsTesting입니다. intent, entity, enum, query, 시스템 통합을 모두 테스트할 수 있는 단일 프레임워크로, 모두 프로세스 외부에서, 모두 자동화됩니다.
CometCal 샘플 프로젝트를 다운로드하여 직접 사용해보고, 앱의 전체 테스트 세트를 탐색해 보세요.
이 강연은 AppIntentsTesting이 할 수 있는 것의 일부만 다뤘습니다. 전체 API 레퍼런스는 문서를 확인하세요. App Intents로 Siri 경험을 구축하는 방법을 배우고 싶다면, 이 강연을 확인하세요. 이제 세상을 뛰어넘는 무언가를 만들 준비가 되었습니다! 시청해 주셔서 감사합니다!
-
-
6:48 - Your first test: execute an intent
import AppIntentsTesting func testCreateCalendar() async throws { let definitions = IntentDefinitions(bundleIdentifier: "com.example.apple-samplecode.CometCal") let createCalendar = definitions.intents["CreateCalendarIntent"] let result = try await createCalendar.makeIntent( name: "Occupy Saturn", color: "red" ).run() XCTAssertEqual(try result.value.title, "Occupy Saturn") } -
12:25 - Test an entity string query
// Testing Entity string queries func testEventStringQuery() async throws { let results = try await eventEntityDefinition .entities(matching: "Cosmic Ray") XCTAssertEqual(results.count, 1) XCTAssertEqual(try results[0].title, "Cosmic Ray Calibration") } -
13:00 - Implement the EntityStringQuery under test
// Updated query implementation struct EventEntityQuery: EntityStringQuery { func entities(for identifiers: [EventEntity.ID]) async throws -> [EventEntity] { } func suggestedEntities() async throws -> [EventEntity] { } func entities(matching string: String) async throws -> [EventEntity] { try calendarManager.fetchEvents() .filter { $0.title.localizedCaseInsensitiveContains(string) } .map(\.entity) } } -
15:42 - Chain multiple intents in one test
// Test event creation followed by update func testCreateAndUpdateEvent() async throws { let createResult = try await createEventDefinition.makeIntent( title: "Asteroid Dodgeball Practice", startDate: Date(), isAllDay: false, calendar: "Deep Space" ).run() XCTAssertEqual(try createResult.value.title, "Asteroid Dodgeball Practice") let updateResult = try await updateEventDefinition.makeIntent( title: "Asteroid Dodgeball Rules Overview", event: createResult.value ).run() XCTAssertEqual(try updateResult.value.title, "Asteroid Dodgeball Rules Overview") } -
17:45 - Make an intent test-only
// Test-only intent: SeedSampleEventsIntent #if DEBUG struct SeedSampleEventsIntent: AppIntent { static let isDiscoverable = false func perform() async throws -> some IntentResult { // Create known list of events return .result() } } #endif -
20:27 - Test Spotlight indexing
// Testing Spotlight indexing func testNewEventIndexedInSpotlight() async throws { let before = try await eventEntityDefinition.spotlightQuery("Supernova Viewing Party") XCTAssertTrue(before.isEmpty, "Event should not exist in Spotlight yet") // ... Create "Supernova Viewing Party" Event let after = try await eventEntityDefinition.spotlightQuery("Supernova Viewing Party") XCTAssertEqual(after.count, 1) XCTAssertEqual(try after[0].title, "Supernova Viewing Party") } -
22:33 - Test view annotations
/ Testing view annotations func testEventViewAnnotation() async throws { try await openEventDefinition.makeIntent(target: "Morning Launch Briefing").run() // Confirm correct event page let app = XCUIApplication() let title = app.staticTexts["Morning Launch Briefing"] XCTAssertTrue(title.waitForExistence(timeout: 5)) let annotations = try await eventEntityDefinition.viewAnnotations() XCTAssertEqual(annotations.count, 1, "Expected exactly one view annotation") XCTAssertEqual(try annotations[0].entity.title, "Morning Launch Briefing") }
-
-
- 0:16 - Introduction
AppIntentsTesting, a new framework for testing App Intents, which power Siri, Shortcuts, Spotlight, and Widgets. Outlines the agenda: getting started, framework overview, testing intents and entities, and system integrations.
- 2:01 - Meet CometCal: your first test
Introduces the CometCal sample calendar app and writes a first test for CreateCalendarIntent: create a UI testing bundle, build an IntentDefinitions from the bundle id, call makeIntent with parameters, run() it on-device, and assert on the returned entity via dynamic member lookup.
- 2:29 - How AppIntentsTesting works
An integration testing framework that runs your tests in a standard XCUITest bundle while the app runs in a separate process, executing intents through the full App Intents stack, with no mocks and no app-code imports (just a bundle id), giving stable, CI-friendly tests.
- 9:39 - Testing entity queries
Test-driven development of an EntityStringQuery: call entities(matching:) on the entity definition, watch it fail, implement the entities(matching:) conformance on EventEntityQuery, and rerun to green, verified against Shortcuts on device.
- 13:49 - Combining multiple intents
Chain intents in one test as people compose Shortcuts: run CreateEventIntent (with the runtime resolving a CalendarEntity from a string), then pass the returned EventEntity straight into UpdateEventIntent and assert the updated title.
- 16:27 - Test-only intents
Focused intents that exist only for tests, to seed known state, jump directly to a view, or wrap not-yet-adopted functionality. Make any intent test-only with isDiscoverable: false and an #if DEBUG guard.
- 18:22 - Testing Spotlight indexing
Write a regression test for Spotlight: spotlightQuery() returns nothing before the event exists, create it with CreateEventIntent, then assert exactly one indexed result, catching the real bug where indexing was commented out.
- 20:56 - Testing view annotations
Verify the entity Siri sees on screen: open an event via OpenEventIntent, use XCUI to confirm the page, call viewAnnotations(), and assert the single ViewAnnotation's entity, surfacing a bug where the wrong EntityIdentifier (calendar id) was used.
- 24:00 - The App Intents testing workflow
Where AppIntentsTesting fits: unit-test the fundamental types (actions, data, queries) first, then integration-test deeper system reaches (view annotations, Spotlight, cross-app), and still test manually with Siri and Shortcuts.
- 25:19 - Next steps
Download the CometCal sample project and its full test suite, consult the AppIntentsTesting documentation for the complete API, and explore the talk on building Siri experiences with App Intents.