Last active
June 12, 2024 09:42
-
-
Save janodev/ebff9d825e21a238fa5c1720a91fef20 to your computer and use it in GitHub Desktop.
Summary of the Testing framework presented in WWDC 2024
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
Sessions: | |
- Meet Swift Testing | |
https://www.youtube.com/watch?v=WFnkNcvLnCI | |
- Go further with Swift Testing | |
https://www.youtube.com/watch?v=bOvWGHi-BxI | |
There are three assertions that replace all XCTAssert functions: | |
#expect() | |
#expect(throws:) | |
#require() | |
*/ | |
// test passes if expression returns true | |
#expect(Color.black.toHex() == "000000") | |
// test passes if expression throws | |
#expect(throws: ThemeError.self) { | |
try convertToHex(Color.black) | |
} | |
// test ends early if value is nil. Similar to XCTUnwrap. | |
_ = try #require(Int("1")) | |
// another way | |
do { | |
_ = try convertToHex(Color.black) | |
} catch ThemeError.wrongColor { | |
} catch { | |
Issue.record(error, "Unexpected error") | |
} | |
// test passes if #require fails | |
withKnownIssue { | |
_ = try #require(Int("A")) | |
} | |
// ORGANIZING TESTS | |
// All assertions must be inside a function annotated with @Test | |
@Test | |
func colorToHex() throws { | |
#expect(Color.black.toHex() == "000000") | |
} | |
// A test function may be global or be grouped inside a struct, actor, or class. | |
struct Colors { | |
@Test | |
func colorToHex() throws { | |
#expect(Color.black.toHex() == "000000") | |
} | |
} | |
// ORGANIZING TESTS IN SUITES | |
// Optionally you can label them adding @Suite. | |
@Suite("A test demonstration") | |
struct TestSuite { | |
// ... | |
} | |
// Test run in parallel and random order unless you pass '.serialized'. | |
@Suite("A test demonstration", .serialized) | |
struct TestSuite { | |
// nested suites will inherit the .serialized argument | |
@Suite struct TestSuite { ... } | |
} | |
// ORGANIZING TESTS WITH TAGS | |
// Extend Tag to create custom tags | |
extension Tag { | |
@Tag static var caffeinated: Self | |
@Tag static var chocolatey: Self | |
} | |
// then you can tag suites and tests | |
@Suite(.tags(.caffeinated)) | |
struct OneMoreSuite { | |
@Test(.tags(.caffeinated, .chocolatey)) | |
func whatever() {/*...*/} | |
} | |
// Recap: test can be organized using Suites, nested Suites, and tags. | |
// Use filter in the test navigator and Test plans to select which tests to run. | |
// TEST TRAITS | |
// @Test("Custom name") Custom name | |
// @Test(.bug("myjira.com/issues/999", "Title") Related bug report | |
// @Test(.tags(.critical)) Custom tag | |
// @Test(.enabled(if: Server.isOnline)) Enabled by runtime condition | |
// @Test(.disabled("Currently broken")) Disabled | |
// @Test(.timeLimit(.minutes(3))) Maximum time | |
// @Test @available(macOS 15, *) Limit the test to certain OS versions | |
// PARAMETERIZING FUNCTIONS | |
// This calls the function three times. | |
// (first enum case passed as argument needed the explicit type, the rest were inferred) | |
@Test(arguments: [Flavor.vanilla, .chocolate, .strawberry]) | |
func doesNotContainNuts1(flavor: Flavor) throws { | |
try #require(!flavor.containsNuts) | |
} | |
// Noteworthy: | |
// - Enums can also be passed as allCases if they support CaseIterable. | |
// - You may also pass Array, Set, OptionSet, Dictionary, and Range. | |
// - Tests can be parameterized with a maximum of two collections. | |
// This calls with all permutations of the possible values for each argument. | |
@Test(arguments: Flavor.allCases, Dish.allCases) | |
func doesNotContainNuts2(flavor: Flavor, dish: Dish) throws { | |
try #require(!flavor.containsNuts) | |
} | |
// This makes pairs, then calls with each pair. | |
@Test(arguments: zip(Flavor.allCases, Dish.allCases)) | |
func doesNotContainNuts2(flavor: Flavor, dish: Dish) throws { | |
try #require(!flavor.containsNuts) | |
} | |
// TESTING ASYNCHRONOUS CONDITIONS | |
// testing async calls | |
@Test func bakeCookies() async throws { | |
let cookies = await Cookie.bake(count: 10) | |
#expect(cookies.count == 10) | |
} | |
// testing completion handlers | |
@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 if let error { | |
continuation.resume(throwing: error) | |
} | |
} | |
} | |
} | |
// testing number of calls | |
@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 | |
ateCookie() | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment