Testing in Swift

Posted on Sun 10 June 2018 in Swift

We want to be able to write tests in Swift, and to do this we will use the XCTest framework.

We'll start by writing a function to test, something we haven't actually done yet.

(All the code here is available from [Github](

// add-function.swift  [Swift 4.1, Xcode 9.3.1, 2018-06-10]
//
// Trivial demonstration of function syntax


func add(a: Int, b: Int) -> Int {
    return a + b
}

func saybye() {
    print("Bye!")
}


print("2 + 3 = \(add(a: 2, b: 3))")
print("2 + -2 = \(add(a: -2, b: 2))")
saybye()

There's not a huge amount to say about this, but in case it isn't clear:

  • The keyword func introduces a function definition.
  • Arguments for the function are declared as name:type.
  • The return type for the function is declared after the -> symbol.
  • If there is no value to be returned, the -> can be omitted, as in the second function, which also takes no arguments.
  • The function body is enclosed in braces.
  • We're using the \(expression) syntax to embed a calculated value in a string (with automatic type co-ercion).
  • When passing arguments to the function, we have to name them, but (despite this) they have to be passed in the declared order (you're not writing Python yet!)1

If we run this, the output is as expected:

$ ./add-function
2 + 3 = 5
2 + -2 = 0
Bye!

Naming the arguments when calling the function here seems to serve no useful purpose: there's nothing special about a and b, and requiring them to be named merely makes it harder to remember how to invoke the function. Swift allows us to declare the arguments with an _ prefix (then a space), and if we do so, the arguments do not need to be (and indeed, are not allowed to be) named when calling the function.

So this code is functionally equivalent:

// add-function-no-labels.swift
//
// Trivial demonstration of function syntax with anonymous arguments

func add(_ a: Int, _ b: Int) -> Int {
    return a + b
}

func saybye() {
    print("Bye!")
}

print("2 + 3 = \(add(2, 3))")
print("2 + -2 = \(add(-2, 2))")
saybye()

However, the point of this post wasn't really functions, but rather testing. And for that, we need to learn a little about packaging in Swift.

XCTest, as the name implies, comes from Xcode, and expects your Swift sources, tests, and outputs all to have a very specific structure. This carries over to working with Swift from the command line. By far the easiest way to proceed (outside Xcode) is to use the swift package command Apple supplies with the Xcode command-line tools. To do this, we first need to create a directory to create our package, then use the swift package command to initialize the structure of that package, like this:

$ mkdir adder
$ cd adder
$ swift package init --type library
Creating library package: adder
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/adder/adder.swift
Creating Tests/
Creating Tests/LinuxMain.swift
Creating Tests/adderTests/
Creating Tests/adderTests/adderTests.swift
Creating Tests/adderTests/XCTestManifests.swift

$ ls -R
Package.swift   README.md   Sources     Tests

./Sources:
adder

./Sources/adder:
adder.swift

./Tests:
LinuxMain.swift adderTests

./Tests/adderTests:
XCTestManifests.swift   adderTests.swift

Points to note:
  • The packager has created some helper code for Linux too.
  • It's also created a some trivial code to test and a single test for it.
  • We've specified that we're building a library here, but we could have said we want an executable instead.

We could run the auto-generated test for the auto-generated code its tests for its code by saying:

$ swift test

but, we'll replace the code and test before doing so.

For the code, we'll simply use the add function from above:

// adder.swift  [Swift 4.1, Xcode 9.3.1, 2018-06-10]
//
// Simple integer add function for testing

func add(a: Int, b: Int) -> Int {
    return a + b
}

using this to replace the "Hello, World"-ish that the packager generated for us.

import XCTest
@testable import adder

class adderTests: XCTestCase {
    func testAddSmallIntegers() {
        XCTAssertEqual(adder.add(a: 2, b:3), 5)
    }

    func testAddInverses() {
        XCTAssertEqual(adder.add(a: 2, b:-2), 0)
        XCTAssertEqual(adder.add(a: 10000000, b:-10000000), 0)
    }
}

Points to note:

  • We need the @testable before the import of our module to make it available to the test framework.2
  • We subclass XCTestCase class from XCTest to form a test class, (following the usual x-unit pattern)
  • We use the XCAssertEqual function, from XCTest for our assertions.
  • The generated test code made the test class final, which prevents it from being subclassed. I'm not sure what the benefit of that is, so I've omitted it, but it would do no harm
  • We need to prefix our add function with the namespace qualifier adder. when calling it in our tests. I am currently confused as to why this is the case, as everything I've read seems to suggest we shouldn't need to. We don't seem to need to prefix functions from Foundation with Foundation. when we call them, so either the way we've defined or imported the library here is suboptimal, or there's a name clash I'm unaware of, or there's something else I've misunderstood. We'll come back to this (and I'll update this post) when I've understood more about Swift packaging and namespaces.
  • The generated test code also include the lines:
static var allTests = [
    ("testExample", testExample),
]

It looks as if that is necessary only for Linux (see here), so again, I've omitted it here.

This is what happens if we run the tests:

$ swift test
Compile Swift Module 'adderTests' (2 sources)
Test Suite 'All tests' started at 2018-06-09 13:03:53.957
Test Suite 'adderPackageTests.xctest' started at 2018-06-09 13:03:53.957
Test Suite 'adderTests' started at 2018-06-09 13:03:53.957
Test Case '-[adderTests.adderTests testAddInverses]' started.
Test Case '-[adderTests.adderTests testAddInverses]' passed (0.110 seconds).
Test Case '-[adderTests.adderTests testAddSmallIntegers]' started.
Test Case '-[adderTests.adderTests testAddSmallIntegers]' passed (0.000 seconds).
Test Suite 'adderTests' passed at 2018-06-09 13:03:54.067.
     Executed 2 tests, with 0 failures (0 unexpected) in 0.110 (0.111) seconds
Test Suite 'adderPackageTests.xctest' passed at 2018-06-09 13:03:54.068.
     Executed 2 tests, with 0 failures (0 unexpected) in 0.110 (0.111) seconds
Test Suite 'All tests' passed at 2018-06-09 13:03:54.068.
     Executed 2 tests, with 0 failures (0 unexpected) in 0.110 (0.111) seconds

Happily, both tests pass.

Before concluding, we'll just show what happens if we have create a failing test, for example by changing the first test to expect 0 instead of 5 for the addition of 2 and 3.

$ swift test
Compile Swift Module 'adderTests' (2 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/adderPackageTests.xctest/Contents/MacOS/adderPackageTests
Test Suite 'All tests' started at 2018-06-09 13:13:56.386
Test Suite 'adderPackageTests.xctest' started at 2018-06-09 13:13:56.386
Test Suite 'adderTests' started at 2018-06-09 13:13:56.386
Test Case '-[adderTests.adderTests testAddInverses]' started.
Test Case '-[adderTests.adderTests testAddInverses]' passed (0.114 seconds).
Test Case '-[adderTests.adderTests testAddSmallIntegers]' started.
/Users/njr/lang/swift/cli-swift-examples/adder/Tests/adderTests/adderTests.swift:6: error: -[adderTests.adderTests testAddSmallIntegers] : XCTAssertEqual failed: ("5") is not equal to ("0") - 
Test Case '-[adderTests.adderTests testAddSmallIntegers]' failed (0.002 seconds).
Test Suite 'adderTests' failed at 2018-06-09 13:13:56.502.
     Executed 2 tests, with 1 failure (0 unexpected) in 0.116 (0.116) seconds
Test Suite 'adderPackageTests.xctest' failed at 2018-06-09 13:13:56.502.
     Executed 2 tests, with 1 failure (0 unexpected) in 0.116 (0.116) seconds
Test Suite 'All tests' failed at 2018-06-09 13:13:56.502.
     Executed 2 tests, with 1 failure (0 unexpected) in 0.116 (0.117) seconds
$

The output is a little but busy, but we can see that the crucial information about the failure is as follows (in which I've broken the single-line error message to enhance readability):

/Users/njr/lang/swift/cli-swift-examples/adder/Tests/adderTests/adderTests.swift:6:
error: -[adderTests.adderTests testAddSmallIntegers] : XCTAssertEqual failed:
("5") is not equal to ("0")

I'm slightly surprised at the way the failure is reporting the failure—making it look as though the values are strings—but the problem is clear.

That's probably enough for one post.


  1. Swift's approach to function and argument names is clearly strongly influence by Objective C, which was itself strongly indludenced by Smalltalk. In a previous post we saw an example of a method for percent-encoding a string, which we called as path.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlPathAllowed). This style of naming functions as verbs and arguments as parts of sentences explaining the qualification that the provide to the verb is idomatic, and went even further in Objective-C, where the "function" (actaually a message) would actually be called addPercentEncoding:withAllowedCharacters

  2. I'm not sure, at this point, what @testtable actually does, and have been a bit surprised at the paucity of information on it. I suspect it's not needed in Xcode, where most people develop Swift. But I can confirm that the tests will not run without this.