BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Swift Testing is a New Framework from Apple to Modernize Testing for Swift across Platforms

Swift Testing is a New Framework from Apple to Modernize Testing for Swift across Platforms

While XCTest remains the preferred way to create tests in Xcode, the new Swift Testing framework attempts to introduce an expressive and intuitive API for the definition of tests that applies to all platforms where Swift is supported. The framework also enables parametrizing, parallelizing, categorizing, and associating tests with bugs.

Swift Testing makes extensive use of macros to provide an idiomatic way to declare complex behaviors. For example, this is how you declare a test with a name and a set of alternative inputs it should be run against:

import Testing
...
@Test("Sample test", arguments: [
    "input string 1",
    "input string 2",
    "input string 3"
])
func sampleTest(sampleInput: String) {
    ...
}

The above test will be executed three times, with each call receiving one of the provided arguments through the sampleInput parameter. When parametrizing tests, you can also handle more than one input. In this case, you will provide a list of collections for @Test's arguments and the test function will take an argument from each collection for every possible combination of values from the two collections:

@Test("Multiple collections as test inputs", arguments: 0...100, ["a", ..., "z"])
func test(number: Int, letter: String) {
}

Under specific conditions for the arguments type, it is possible to run parametrized tests selectively, so you do not have to run tests for all possible combinations of input values when just a few of them fail.

Besides optionally specifying a name and a list of arguments, the @Test macro supports the possibility of associating traits to a test function. Existing traits make it possible to enable or disable tests based on a runtime condition, limit the running time of tests, run tests serially or in parallel, associate tests to bugs, and more. For example, this is how you can run a test only if a given app feature is enabled:

@Test(.enabled(if: App.ConditionToMatch))
func conditionalTest() {
    ...
}

Developers can also define their own traits by implementing types conforming to TestTrait.

Parametrizing tests has the additional advantage that the diagnostics produced in case of failure clearly indicates the input that failed.

Test functions can be organized into test suites. The traditional way of doing this is by placing them in the same test class. Swift Testing also provides a specific macro to that aim, @Suite. All test functions belonging to a test suite inherit the same traits from the suite, including tags(_:) or disabled(_:sourceLocation:).

To validate test results, Swift Testing provides two macros expect(_:_:sourceLocation:) and require(_:_:sourceLocation:). While require stops the test execution when its condition is not met by throwing an exception, expect prints detailed information.

One important caveat is test functions aren’t stripped from binaries when building for release. Due to this, Apple strongly advises to import the testing library into a test target not linked with your main executable.

Swift Testing is available on GitHub and on the Swift Package Index, and requires Swift 6. XCTest and Swift Testing can coexist in the same project, so you do not necessarily need to migrate your existing tests if you want to adopt the new framework.

About the Author

Rate this Article

Adoption
Style

BT