Go further with Swift Testing - WWDC24 - Videos - Apple Developer (2024)

More Videos

Streaming is available in most browsers,
and in the Developer app.

  • Overview
  • Code
  • Learn how to write a sweet set of (test) suites using Swift Testing's baked-in features. Discover how to take the building blocks further and use them to help expand tests to cover more scenarios, organize your tests across different suites, and optimize your tests to run in parallel.

    Chapters

    • 0:00 - Introduction
    • 0:36 - Why we write tests
    • 0:51 - Challenges in testing
    • 1:21 - Writing expressive code
    • 1:35 - Expectations
    • 3:58 - Required expectations
    • 4:29 - Tests with known issues
    • 5:54 - Custom test descriptions
    • 7:23 - Parameterized testing
    • 12:47 - Organizing tests
    • 12:58 - Test suites
    • 13:33 - The tag trait
    • 20:38 - Xcode Cloud support
    • 21:09 - Testing in parallel
    • 21:36 - Parallel testing basics
    • 24:26 - Asynchronous conditions
    • 26:32 - Wrap up

    Resources

    Related Videos

    WWDC24

    • Meet Swift Testing

    WWDC21

    • Meet async/await in Swift
  • Download

    Array
    • 0:01 - Successful throwing function

      // Expecting errorsimport Testing@Test func brewTeaSuccessfully() throws { let teaLeaves = TeaLeaves(name: "EarlGrey", optimalBrewTime: 4) let cupOfTea = try teaLeaves.brew(forMinutes: 3)}
    • 0:02 - Validating a successful throwing function

      import Testing@Test func brewTeaSuccessfully() throws { let teaLeaves = TeaLeaves(name: "EarlGrey", optimalBrewTime: 4) let cupOfTea = try teaLeaves.brew(forMinutes: 3) #expect(cupOfTea.quality == .perfect)}
    • 0:03 - Validating an error is thrown with do-catch (not recommended)

      import Testing@Test func brewTeaError() throws { let teaLeaves = TeaLeaves(name: "EarlGrey", optimalBrewTime: 3) do { try teaLeaves.brew(forMinutes: 100) } catch is BrewingError { // This is the code path we are expecting } catch { Issue.record("Unexpected Error") }}
    • 0:04 - Validating a general error is thrown

      import Testing@Test func brewTeaError() throws { let teaLeaves = TeaLeaves(name: "EarlGrey", optimalBrewTime: 4) #expect(throws: (any Error).self) { try teaLeaves.brew(forMinutes: 200) // We don't want this to fail the test! }}
    • 0:05 - Validating a type of error

      import Testing@Test func brewTeaError() throws { let teaLeaves = TeaLeaves(name: "EarlGrey", optimalBrewTime: 4) #expect(throws: BrewingError.self) { try teaLeaves.brew(forMinutes: 200) // We don't want this to fail the test! }}
    • 0:06 - Validating a specific error

      import Testing@Test func brewTeaError() throws { let teaLeaves = TeaLeaves(name: "EarlGrey", optimalBrewTime: 4) #expect(throws: BrewingError.oversteeped) { try teaLeaves.brew(forMinutes: 200) // We don't want this to fail the test! }}
    • 0:07 - Complicated validations

      import Testing@Test func brewTea() { let teaLeaves = TeaLeaves(name: "EarlGrey", optimalBrewTime: 4) #expect { try teaLeaves.brew(forMinutes: 3) } throws: { error in guard let error = error as? BrewingError, case let .needsMoreTime(optimalBrewTime) = error else { return false } return optimalBrewTime == 4 }}
    • 0:08 - Throwing expectation

      import Testing@Test func brewAllGreenTeas() { #expect(throws: BrewingError.self) { brewMultipleTeas(teaLeaves: ["Sencha", "EarlGrey", "Jasmine"], time: 2) }}
    • 0:09 - Required expectations

      import Testing@Test func brewAllGreenTeas() throws { try #require(throws: BrewingError.self) { brewMultipleTeas(teaLeaves: ["Sencha", "EarlGrey", "Jasmine"], time: 2) }}
    • 0:10 - Control flow of validating an optional value (not recommended)

      import Testingstruct TeaLeaves {symbols let name: String let optimalBrewTime: Int func brew(forMinutes minutes: Int) throws -> Tea { ... }}@Test func brewTea() throws { let teaLeaves = TeaLeaves(name: "Sencha", optimalBrewTime: 2) let brewedTea = try teaLeaves.brew(forMinutes: 100) guard let color = brewedTea.color else { Issue.record("Tea color was not available!") } #expect(color == .green)}
    • 0:11 - Failing test with a throwing function

      import Testing@Test func softServeIceCreamInCone() throws { try softServeMachine.makeSoftServe(in: .cone)}
    • 0:12 - Disabling a test with a throwing function (not recommended)

      import Testing@Test(.disabled) func softServeIceCreamInCone() throws { try softServeMachine.makeSoftServe(in: .cone)}
    • 0:13 - Wrapping a failing test in withKnownIssue

      import Testing@Test func softServeIceCreamInCone() throws { withKnownIssue { try softServeMachine.makeSoftServe(in: .cone) }}
    • 0:14 - Wrap just the failing section in withKnownIssue

      import Testing@Test func softServeIceCreamInCone() throws { let iceCreamBatter = IceCreamBatter(flavor: .chocolate) try #require(iceCreamBatter != nil) #expect(iceCreamBatter.flavor == .chocolate) withKnownIssue { try softServeMachine.makeSoftServe(in: .cone) }}
    • 0:15 - Simple enumerations

      import Testingenum SoftServe { case vanilla, chocolate, pineapple}
    • 0:16 - Complex types

      import Testingstruct SoftServe { let flavor: Flavor let container: Container let toppings: [Topping]}@Test(arguments: [ SoftServe(flavor: .vanilla, container: .cone, toppings: [.sprinkles]), SoftServe(flavor: .chocolate, container: .cone, toppings: [.sprinkles]), SoftServe(flavor: .pineapple, container: .cup, toppings: [.whippedCream])])func softServeFlavors(_ softServe: SoftServe) { /*...*/ }
    • 0:17 - Conforming to CustomTestStringConvertible

      import Testingstruct SoftServe: CustomTestStringConvertible { let flavor: Flavor let container: Container let toppings: [Topping] var testDescription: String { "\(flavor) in a \(container)" }}@Test(arguments: [ SoftServe(flavor: .vanilla, container: .cone, toppings: [.sprinkles]), SoftServe(flavor: .chocolate, container: .cone, toppings: [.sprinkles]), SoftServe(flavor: .pineapple, container: .cup, toppings: [.whippedCream])])func softServeFlavors(_ softServe: SoftServe) { /*...*/ }
    • 0:18 - An enumeration with a computed property

      extension IceCream { enum Flavor { case vanilla, chocolate, strawberry, mintChip, rockyRoad, pistachio var containsNuts: Bool { switch self { case .rockyRoad, .pistachio: return true default: return false } } }}
    • 0:19 - A test function for a specific case of an enumeration

      import Testing@Test func doesVanillaContainNuts() throws { try #require(!IceCream.Flavor.vanilla.containsNuts)}
    • 0:20 - Separate test functions for all cases of an enumeration

      import Testing@Test func doesVanillaContainNuts() throws { try #require(!IceCream.Flavor.vanilla.containsNuts)}@Test func doesChocolateContainNuts() throws { try #require(!IceCream.Flavor.chocolate.containsNuts)}@Test func doesStrawberryContainNuts() throws { try #require(!IceCream.Flavor.strawberry.containsNuts)}@Test func doesMintChipContainNuts() throws { try #require(!IceCream.Flavor.mintChip.containsNuts)}@Test func doesRockyRoadContainNuts() throws { try #require(!IceCream.Flavor.rockyRoad.containsNuts)}
    • 0:21 - Parameterizing a test with a for loop (not recommended)

      import Testingextension IceCream { enum Flavor { case vanilla, chocolate, strawberry, mintChip, rockyRoad, pistachio }}@Testfunc doesNotContainNuts() throws { for flavor in [IceCream.Flavor.vanilla, .chocolate, .strawberry, .mintChip] { try #require(!flavor.containsNuts) }}
    • 0:22 - Swift testing parameterized tests

      import Testingextension IceCream { enum Flavor { case vanilla, chocolate, strawberry, mintChip, rockyRoad, pistachio }}@Test(arguments: [IceCream.Flavor.vanilla, .chocolate, .strawberry, .mintChip])func doesNotContainNuts(flavor: IceCream.Flavor) throws { try #require(!flavor.containsNuts)}
    • 0:23 - 100% test coverage

      import Testingextension IceCream { enum Flavor { case vanilla, chocolate, strawberry, mintChip, rockyRoad, pistachio }}@Test(arguments: [IceCream.Flavor.vanilla, .chocolate, .strawberry, .mintChip])func doesNotContainNuts(flavor: IceCream.Flavor) throws { try #require(!flavor.containsNuts)}@Test(arguments: [IceCream.Flavor.rockyRoad, .pistachio])func containNuts(flavor: IceCream.Flavor) { #expect(flavor.containsNuts)}
    • 0:24 - A parameterized test with one argument

      import Testingenum Ingredient: CaseIterable { case rice, potato, lettuce, egg}@Test(arguments: Ingredient.allCases)func cook(_ ingredient: Ingredient) async throws { #expect(ingredient.isFresh) let result = try cook(ingredient) try #require(result.isDelicious)}
    • 0:26 - Adding a second argument to a parameterized test

      import Testingenum Ingredient: CaseIterable { case rice, potato, lettuce, egg}enum Dish: CaseIterable { case onigiri, fries, salad, omelette}@Test(arguments: Ingredient.allCases, Dish.allCases)func cook(_ ingredient: Ingredient, into dish: Dish) async throws { #expect(ingredient.isFresh) let result = try cook(ingredient) try #require(result.isDelicious) try #require(result == dish)}
    • 0:28 - Using zip() on arguments

      import Testingenum Ingredient: CaseIterable { case rice, potato, lettuce, egg}enum Dish: CaseIterable { case onigiri, fries, salad, omelette}@Test(arguments: zip(Ingredient.allCases, Dish.allCases))func cook(_ ingredient: Ingredient, into dish: Dish) async throws { #expect(ingredient.isFresh) let result = try cook(ingredient) try #require(result.isDelicious) try #require(result == dish)}
    • 0:29 - Suites

      @Suite("Various desserts") struct DessertTests { @Test func applePieCrustLayers() { /* ... */ } @Test func lavaCakeBakingTime() { /* ... */ } @Test func eggWaffleFlavors() { /* ... */ } @Test func cheesecakeBakingStrategy() { /* ... */ } @Test func mangoSagoToppings() { /* ... */ } @Test func bananaSplitMinimumScoop() { /* ... */ }}
    • 0:30 - Nested suites

      import Testing@Suite("Various desserts")struct DessertTests { @Suite struct WarmDesserts { @Test func applePieCrustLayers() { /* ... */ } @Test func lavaCakeBakingTime() { /* ... */ } @Test func eggWaffleFlavors() { /* ... */ } } @Suite struct ColdDesserts { @Test func cheesecakeBakingStrategy() { /* ... */ } @Test func mangoSagoToppings() { /* ... */ } @Test func bananaSplitMinimumScoop() { /* ... */ } }}
    • 0:31 - Separate suites

      @Suite struct DrinkTests { @Test func espressoExtractionTime() { /* ... */ } @Test func greenTeaBrewTime() { /* ... */ } @Test func mochaIngredientProportion() { /* ... */ }}@Suite struct DessertTests { @Test func espressoBrownieTexture() { /* ... */ } @Test func bungeoppangFilling() { /* ... */ } @Test func fruitMochiFlavors() { /* ... */ }}
    • 0:32 - Separate suites

      @Suite struct DrinkTests { @Test func espressoExtractionTime() { /* ... */ } @Test func greenTeaBrewTime() { /* ... */ } @Test func mochaIngredientProportion() { /* ... */ }}@Suite struct DessertTests { @Test func espressoBrownieTexture() { /* ... */ } @Test func bungeoppangFilling() { /* ... */ } @Test func fruitMochiFlavors() { /* ... */ }}
    • 0:35 - Using a tag

      import Testing extension Tag { @Tag static var caffeinated: Self}@Suite(.tags(.caffeinated)) struct DrinkTests { @Test func espressoExtractionTime() { /* ... */ } @Test func greenTeaBrewTime() { /* ... */ } @Test func mochaIngredientProportion() { /* ... */ }}@Suite struct DessertTests { @Test(.tags(.caffeinated)) func espressoBrownieTexture() { /* ... */ } @Test func bungeoppangFilling() { /* ... */ } @Test func fruitMochiFlavors() { /* ... */ }}
    • 0:36 - Declare and use a second tag

      import Testing extension Tag { @Tag static var caffeinated: Self @Tag static var chocolatey: Self}@Suite(.tags(.caffeinated)) struct DrinkTests { @Test func espressoExtractionTime() { /* ... */ } @Test func greenTeaBrewTime() { /* ... */ } @Test(.tags(.chocolatey)) func mochaIngredientProportion() { /* ... */ }}@Suite struct DessertTests { @Test(.tags(.caffeinated, .chocolatey)) func espressoBrownieTexture() { /* ... */ } @Test func bungeoppangFilling() { /* ... */ } @Test func fruitMochiFlavors() { /* ... */ }}
    • 0:37 - Two tests with an unintended data dependency (not recommended)

      import Testing// ❌ This code is not concurrency-safe.var cupcake: Cupcake? = nil@Test func bakeCupcake() async { cupcake = await Cupcake.bake(toppedWith: .frosting) // ...}@Test func eatCupcake() async { await eat(cupcake!) // ...}
    • 0:38 - Serialized trait

      import Testing@Suite("Cupcake tests", .serialized)struct CupcakeTests { var cupcake: Cupcake? @Test func mixingIngredients() { /* ... */ } @Test func baking() { /* ... */ } @Test func decorating() { /* ... */ } @Test func eating() { /* ... */ }}
    • 0:39 - Serialized trait with nested suites

      import Testing@Suite("Cupcake tests", .serialized)struct CupcakeTests { var cupcake: Cupcake? @Suite("Mini birthday cupcake tests") struct MiniBirthdayCupcakeTests { // ... } @Test(arguments: [...]) func mixing(ingredient: Food) { /* ... */ } @Test func baking() { /* ... */ } @Test func decorating() { /* ... */ } @Test func eating() { /* ... */ }}
    • 0:40 - Using async/await in a test

      import Testing@Test func bakeCookies() async throws { let cookies = await Cookie.bake(count: 10) try await eat(cookies, with: .milk)}
    • 0:41 - Using a function with a completion handler in a test (not recommended)

      import Testing@Test func bakeCookies() async throws { let cookies = await Cookie.bake(count: 10) // ❌ This code will run after the test function returns. eat(cookies, with: .milk) { result, error in #expect(result != nil) }}
    • 0:42 - Replacing a completion handler with an asynchronous function call

      import Testing@Test func bakeCookies() async throws { let cookies = await Cookie.bake(count: 10) try await eat(cookies, with: .milk)}
    • 0:43 - Using withCheckedThrowingContinuation

      import Testing@Test func bakeCookies() async throws { let cookies = await Cookie.bake(count: 10) try await withCheckedThrowingContinuation { continuation in eat(cookies, with: .milk) { result, error in if let result { continuation.resume(returning: result) } else { continuation.resume(throwing: error) } } }}
    • 0:44 - Callback that invokes more than once (not recommended)

      import Testing@Test func bakeCookies() async throws { let cookies = await Cookie.bake(count: 10) // ❌ This code is not concurrency-safe. var cookiesEaten = 0 try await eat(cookies, with: .milk) { cookie, crumbs in #expect(!crumbs.in(.milk)) cookiesEaten += 1 } #expect(cookiesEaten == 10)}
    • 0:45 - Confirmations on callbacks that invoke more than once

      import Testing@Test func bakeCookies() async throws { let cookies = await Cookie.bake(count: 10) try await confirmation("Ate cookies", expectedCount: 10) { ateCookie in try await eat(cookies, with: .milk) { cookie, crumbs in #expect(!crumbs.in(.milk)) ateCookie() } }}
    • 0:46 - Confirmation that occurs 0 times

      import Testing@Test func bakeCookies() async throws { let cookies = await Cookie.bake(count: 10) try await confirmation("Ate cookies", expectedCount: 0) { ateCookie in try await eat(cookies, with: .milk) { cookie, crumbs in #expect(!crumbs.in(.milk)) ateCookie() } }}
  • Looking for something specific? Enter a topic above and jump straight to the good stuff.

    Go further with Swift Testing - WWDC24 - Videos - Apple Developer (2024)

    FAQs

    Does Apple developer license need to be renewed every year? ›

    Membership is provided on an annual basis as an auto-renewable subscription that renews until cancelled. You can make your purchase using one of your Apple ID payment methods. Apple Account balances (from redeeming Apple Gift Cards or adding funds) aren't accepted as payment for Apple Developer Program memberships.

    How do I run a test in Swift Xcode? ›

    To run test functions defined under a common type or suite, move the pointer over the diamond icon alongside the type's declaration in the editor gutter, and click the gray play icon. In Swift Testing, a test suite is any type that includes the definition for one or more test functions.

    How do I add a test device to Apple developer? ›

    Required role: Account Holder or Admin.
    1. In Certificates, Identifiers & Profiles, click Devices in the sidebar, then click the add button (+) on the top left.
    2. Select the platform, enter a device name, and the Unique Device Identifier (UDID).
    3. Click Continue.
    4. Review the registration information, then click Register.

    What happens if you don't renew Apple Developer? ›

    Expired memberships

    If your Apple Developer Program membership expires, your apps will no longer be available for download and you won't be able to submit new apps or updates or access Certificates, Identifiers & Profiles.

    How much is Apple yearly developer fee? ›

    Pricing and fees

    The Apple Developer Program is 99 USD per membership year, or in local currency where available.

    Where can I test my swift code? ›

    SwiftPM is integrated with XCTest, Apple's unit test framework. Running swift test from the terminal, or triggering the test action in your IDE (Xcode or similar), will run all of your XCTest test cases. Test results will be displayed in your IDE or printed out to the terminal.

    How is testing done in Swift? ›

    A standard test case contains setup and teardown functions. These functions are called before and after each of the test functions, respectively. This cycle allows us to set up prerequisites before the tests are run and reset any variables, references, etc. after the completion of each unit test.

    How to write Swift unit tests? ›

    To write a unit test case in Swift, use the XCTest framework to create test cases. This involves setting up conditions, executing the code under test, and using assertions to verify the outcomes. Each Swift test case should be named descriptively to indicate their purpose.

    How can I become an Apple tester? ›

    You can sign up for Apple's Beta Software program or Apple Seed project online. Once you get accepted as a product tester, you get to try new programs and features in exchange for valuable feedback.

    How do I connect my iphone to Xcode for testing? ›

    Select the device from the list in the Devices and Simulators window in Xcode and click the pairing button which triggers a code to appear on the target device. Enter the code on the Mac to complete the pairing process.

    How do I send iOS build for testing? ›

    How to Deploy an iOS App to TestFlight?
    1. Step 1: Prepare Your App in Xcode. Launch Xcode. ...
    2. Step 2: Archive Your App. Generate Build. ...
    3. Step 3: Distribute the App. Choose which version of the app you want to Distribute. ...
    4. Step 4: Tester Onboarding. ...
    5. Step 5: Adding the Build for Testing. ...
    6. Step 6: Submit for Review (Optional For Testing)
    Apr 25, 2024

    What happens when Apple Developer certificate expired? ›

    According to Apple documentation, Developer ID Installer Certificate (Mac applications): If your certificate expires, users can no longer launch installer packages for your Mac applications that were signed with this certificate.

    How long does Apple Developer account last? ›

    The paid developer account is $99 per year. The default setting is to auto-renew annually.

    How much does it cost to renew Apple Developer membership? ›

    Completing your enrollment

    The Apple Developer Program annual fee is 99 USD and the Apple Developer Enterprise Program annual fee is 299 USD, in local currency where available. Prices may vary by region and are listed in local currency during the enrollment process.

    How do I renew my Apple Developer certificate? ›

    What to Know
    1. Go to Applications > Utilities > Keychain Access app on a Mac. Delete the expired certificates.
    2. In Keychain Access menu bar, select Certificate Assistant > Request a Certificate from a Certificate Authority.
    3. Enter your email address and name. Select Saved to disk > Continue.
    Jul 27, 2022

    Top Articles
    Latest Posts
    Article information

    Author: Kieth Sipes

    Last Updated:

    Views: 5982

    Rating: 4.7 / 5 (67 voted)

    Reviews: 82% of readers found this page helpful

    Author information

    Name: Kieth Sipes

    Birthday: 2001-04-14

    Address: Suite 492 62479 Champlin Loop, South Catrice, MS 57271

    Phone: +9663362133320

    Job: District Sales Analyst

    Hobby: Digital arts, Dance, Ghost hunting, Worldbuilding, Kayaking, Table tennis, 3D printing

    Introduction: My name is Kieth Sipes, I am a zany, rich, courageous, powerful, faithful, jolly, excited person who loves writing and wants to share my knowledge and understanding with you.