Swift Testing
Software testing is an essential component of software development and plays a crucial role in the quality and reliability of applications. One of the important aspects of testing is unit testing, which verifies individual code units in isolation.
Tests enable early error detection, identifying and resolving issues early in the development process. This saves time and costs by avoiding expensive fixes at later stages. Additionally, tests enhance code quality and maintainability by breaking down code into smaller, testable units, leading to improved architecture and more understandable code. Well-written tests also facilitate long-term software maintenance and help new developers onboard more quickly.
During this year’s WWDC 2024, Apple introduced a new testing framework called “Swift Testing”, which is designed to replace the existing XCTest. The new framework simplifies the writing of unit tests by replacing traditional expectations with macros, and combining all checks within a macro using #expect:
Example: Calculator
Let’s assume we want to test a class named “Calculator.” The class looks like this:
class Calculator {
func add(_ a: Double, _ b: Double) -> Double {
return a + b
}
func subtract(_ a: Double, _ b: Double) -> Double {
return a - b
}
func multiply(_ a: Double, _ b: Double) -> Double {
return a * b
}
func divide(_ a: Double, _ b: Double) -> Double {
return a / b
}
}
First Unit Test
Let’s test our calculator with a first simple unit test that tests the addition of two numbers would look like this:
@Test func addTwoNumbers() async throws {
#expect(calculator.add(1, 1) == 2)
}
Custom Display Name for Test Methods
Since method names can sometimes be long and difficult to read, we can also assign a custom name using the test macro, which will be displayed in the test results in Xcode accordingly:
@Test("Add two numbers") func addTwoNumbers() async throws {
#expect(calculator.add(1, 1) == 2)
}
Suites
By encapsulating multiple tests in a struct, we create a suite.
struct CalculatorTests {
@Test("Add two numbers")
func test_addTwoNumbers() async throws {
#expect(calculator.add(1, 1) == 2)
}
}
Using suites makes the test structure more readable. It is also possible to nest suites within each other, if desired.
Tags
Also new in Swift Testing is the ability to assign tags to each test to combine tests from different suites. When executing tests, you can specifically run all tests with a certain tag. You can create a tag using an extension on “Tag” with the Tag macro, like this:
extension Tag {
@Tag static let edgecase: Self
}
After the creation it can be added to the test trait:
@Test("Division by zero", .tags(.edgecase))
func divideNumberByZero() async throws {
#expect(calculator.divide(6, 0) == nil)
}
Parametrized Testing
Finally, with parametrized testing, tests that follow the same procedure but require different input parameters no longer need to be duplicated.
Consider the following test:
@Test("Multiply two numbers")
func multiplyNumbers() async throws {
#expect(calculator.multiply(1, 2) == 2)
}
If you want to execute the test with different parameters, you can do it like this:
@Test("Multiply two numbers", arguments: [
(1, 1, 1),
(1, 2, 2),
(2, 3, 6),
(10, 20, 200),
])
func multiplyNumbers(a: Double, b: Double, result: Double) async throws {
#expect(calculator.multiply(a, b) == result)
}
Now this test will run with all 4 variants:
In conclusion, Swift Testing represents a significant evolution in Apple’s approach to unit testing, offering developers a more intuitive and efficient way to ensure the quality and reliability of their code. By simplifying test writing with macros and introducing features like custom test method names, test suites, tags, and parameterized testing, Swift Testing not only enhances productivity but also fosters cleaner and more maintainable codebases.
It’s worth noting that Swift Testing is open source and can be accessed on GitHub, inviting collaboration and innovation within the developer community.
There are more features of this framework not covered in this article, so feel free to try it out and let me know your thoughts in the comments.