A first look at a whole new paradigm for unit testing in Swift
I’ve managed to get very rusty at Swift in the months since I graduated from the Apple Developer Academy. So much so that when I started to pick it up again, I had to wait on updates for Xcode and related tools, which was not surprising to me. I also had to set some of my old preferences anew, which was annoying but not surprising.
What was surprising to me was waiting for me the first time in a long time I tried to write a unit test for a Swift class or struct. Even though I was rusty, I was still able to recognize that a new option for testing was available.
When I was going through the academy, the only option seemed to be XCTest, and XCTest felt very similar to JUnit for Java, which I’m very experienced in.
For example, in my astrology app Kepler Star, a work in progress, this is how I tested Element
’s description()
function using XCTest:
func testDescription() {
for element in Element.allCases {
let expected = "\(element)".capitalized
let actual = element.description()
XCTAssertEqual(expected, actual)
}
}
And this is how it failed for me the first time, as I wanted it to:
Then I corrected description()
and saw the test pass. It’s fine. Generally, XCTAssertEqual()
gives informative test failure explanations when tests fail. Not quite so much with XCTAssertTrue()
.
For this next example, I added this stub to Angle
:
// TODO: Write tests for this
static func chooseAngleInSign(_ sign: StarSign) -> Angle {
zero
}
This should be good for Aries, but not for Taurus, Gemini, etc. For now, I’m doing this work in a branch that you won’t see on GitHub.
Next, I wrote a test using XCTest in the same test class as the tests I previously wrote for Angle
.
func testChooseAngleInSign() {
for sign in StarSign.allCases {
let minimum = sign.minimumDegree().getDegrees()
let actual = Angle.chooseAngleInSign(sign).getDegrees()
XCTAssertTrue(actual >= minimum)
}
}
Just as I thought, the eleven test failure explanations were hardly informative.
It was not in this astrology project that I first became aware of the new, simpler option for unit testing Swift structs and classes.
In the other project, when I wanted to make the first test class, I was surprised by what I saw when I right-clicked on the tests folder, selected New File from Template… and scrolled down to the Test category.
I definitely remembered XCTest Unit Test and XCTest UI Test, though I wasn’t sure if I had ever seen Test Plan before, and I was fairly certain I had never seen Swift Testing Unit Test before. So I decided to give it a try.
Coming back to the Kepler Star project, without changing the struct under test, I created a new Swift Testing Unit Test struct and wrote this test:
@Test func testChooseAngleInSign() {
for sign in StarSign.allCases {
let minimum = sign.minimumDegree().getDegrees()
let actual = Angle.chooseAngleInSign(sign).getDegrees()
#expect(actual >= minimum)
}
}
I haven’t yet thought about how to check the given angle is less than the minimum for the next sign (e.g., the selected angle for Aries should be less than 30°, the minimum for Taurus). But this will do for now.
Notice the #expect()
macro. I ran the test and saw that the test failure explanation was a lot more meaningful than the one with XCTAssertTrue()
without my having to provide a message.
I would like this to also include the pertinent sign. But still, it’s an improvement over XCTest. Swift Testing makes it much easier to write tests that give meaningful test failure explanations without having to craft a message.
Besides #expect()
, Swift Testing also provides #require()
. It’s supposed to work the same except that #require()
stops the test even if there is more to run, which is more like what you’d expect with JUnit. Plus there might be some subtleties with exceptions I haven’t really gotten into yet.
In the chooseAngleInSign()
example, seeing that Leo got 0° instead of at least 120°, using #require()
, the test would not have gone on to try Virgo getting at least 150° (not sure if it tried Aries first nor why it tried Taurus after Leo, Virgo and a few others).
Well, that’s the basic idea. Instead of trying to provide a bunch of different assertions like XCTest or JUnit, Swift Testing simply provides a couple of macros that expand on a Boolean condition prior to compilation to create more helpful test failure explanations.
It’s a new paradigm for unit testing Swift projects. I plan to delve into this further and write more articles about it. In the meantime, you can check out Apple’s documentation for Swift Testing.