TDD in Swift Playgrounds

I’ve recently been trying my hand at writing a bit of Swift, and one of the things that I do love is being able to try out code in Playgrounds. It’s especially nice to use Playgrounds for coding in isolation from the rest of an app, which can help keep functionality isolated, avoid tightly coupling code, and other jargon phrases besides.

Unfortunately, I can’t use Playgrounds as much as I’d like, because I like to use Test-Driven Devlopment (TDD), and Playgrounds don’t really play nicely with this.

The problem with using Playgrounds for TDD is that once you’re done with your Playground you move the code into your app and throw the Playground away - and along with it all the validation which demostrates that the code is working correctly. When using TDD you’re supposed keep and run those tests, in order to continually validate that functionality as changes are made in the future.

Wouldn’t it be really cool if somehow you could write your code in a Playground alongside the unit tests which validate the code. That way, once you’ve finished your class you could move the code and the tests back into your app, and get the best of both worlds.

Well, the good news is, you can!

We can do this with a little bit of boilerplate code added to the Playground, allowing us to create and run an XCTestCase unit test class within the Playground itself. Once we have that, we can write our code using all of the TDD practices such as ‘Red-Green-Refactor’, and when the tests all pass, just copy the XCTestCase directly into the unit test target, and the actual code into the main app.

So what does this look like? The actual unit tests look an awful lot like, well, unit tests. For instance, a standard XCTestCase subclass which contains a failing test:

1
2
3
4
5
6
7
8
9
10
11
import XCTest

class MyTests : XCTestCase {
  func testShouldFail() {
      XCTFail("You must fail to succeed!")
  }
  func testShouldPass() {
        XCTAssertEqual(2+2, 4)
    }

}

That’s the test we’re going to be running. Now that we’ve got a failing test, in order to report on it we’ll also need to add a test observer, which will be notified whenever a test fails and print the failure to the console. Then, add the observer to the XCTestObservationCenter to receive test failure notifications:

1
2
3
4
5
6
7
8
9
class PlaygroundTestObserver : NSObject, XCTestObservation {
    @objc func testCase(testCase: XCTestCase, didFailWithDescription description: String, inFile filePath: String?, atLine lineNumber: UInt) {
        print("Test failed on line \(lineNumber): \(testCase.name), \(description)")
    }
}

let observer = PlaygroundTestObserver()
let center = XCTestObservationCenter.sharedTestObservationCenter()
center.addTestObserver(observer)

We’ll also need a way of actually running the tests. Enter the TestRunner, which runs the tests from an XCTestCase and reports on the results. This is done using the same XCTestSuite mechanism which Xcode uses to run unit tests. At the end of the run, a message will be printed to the console, saying how many tests were run, how long it took, and how many of the tests failed.

1
2
3
4
5
6
7
8
9
10
11
12
13
struct TestRunner {

    func runTests(testClass:AnyClass) {
        print("Running test suite \(testClass)")

        let tests = testClass as! XCTestCase.Type
        let testSuite = tests.defaultTestSuite()
        testSuite.runTest()
        let run = testSuite.testRun as! XCTestSuiteRun

        print("Ran \(run.executionCount) tests in \(run.testDuration)s with \(run.totalFailureCount) failures")
    }
}

Lastly, call the runTests() function, passing in the test class to run:

1
TestRunner().runTests(MyTests)

Hey presto! In the Playground console, we now get a simple log with test information:

1
2
3
Running test suite MyTests
Test failed on line 10: -[MyTests testShouldFail], failed - You must fail to succeed!
Ran 2 tests in 0.000755012035369873s with 1 failures

And that’s really all there is to it! I have experimented with extracting the test code into a separate source file, but Playgrounds don’t seem to like importing XCTest in anything other than a main Playground page, so at the moment you need to include the TestRunner and PlaygroundTestObserver in each Playground page in which you wish to write unit tests. I’ve put a sample playground on GitHub which includes the code above, as well as an empty template Playground page, and also a TDD code kata (the Roman Numerals kata) which you can feel free to have a go at and experience the joys of TDD for yourself.

One last great thing about this approach, which you’ll discover if you try out the kata, is that because Playgrounds continually run themselves as you write code, you don’t have to be constantly running your tests manually as you’re going through the TDD process - just write some code and the tests will run magically, giving you instantaneous feedback on the code you’re creating. I cannot express how awesome I think this is.