Swift promises us the best from both imperative and functional languages, but this will affect the way we test our applications. In this talk, Jan Riehn shares his experience of real-world Swift testing, including a 15,000 line Swift app with almost 100% test coverage, started only two weeks after Swift launched.
You can view Jan’s sample tests on GitHub.
Writing your First Test (0:00)
I believe testing is an art. It challenges you, and your abilities. It requires imagination, creativity, and really solid technical skills. Practically, it helps you to write good code, and I think good code lives in the details.
If you have never written a test before, it’s pretty easy. You create a test using Xcode, and there you go! Your first test. For testing in Swift, you will have to import XCTest
, Apple’s testing framework. Your test will have to inherit from XCTestCase
, then you do some initializing for setUp
and tearDown
, create your mocks, and set up your test:
import XCTest
class SummitTest: XCTestCase {
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
}
func testExample() {
XCTAssert(true, "Pass")
}
func testPerformanceExample() {
self.measureBlock() {
}
}
}
Here, we have there a very, very simple testExample
, which just asserts true
. You also have a message, which may help you to find an error, especially in an automated environment. You can also do performance tests; Xcode provides a closure, which you can see in the measureBlock
. It counts the time between the executions.
An Example Test (2:41)
You can now start testing in Swift. Let’s say we want to test a simple HTTP call, perhaps an HTTP GET for swiftsummit.com.
let url = NSURL(string: "http:/swiftsummit.com")!
let task = NSURLSession.sharedSession().dataTaskWithURL(url) {
(data, response, error) in
// process response
}
task.resume()
For this, I use NSURLSession
. You can see that we have data, a response, and an error, and that we process it in the closure. Next, you would start to write a test:
func testShouldReceiveDataFromSwiftSummit() {
//given
let url = NSURL(string: "http:/swiftsummit.com")!
var expectation = expectationWithDescription(“should receive data”)
var dataReceived: NSData? = nil
//when
let task = NSURLSession.sharedSession().dataTaskWithURL(url)
{(data, response, error) in
dataReceived = data
expectation.fulfill()
}
task.resume()
waitForExpectationsWithTimeout(5, handler: nil)
//then
XCTAssertNotNil(dataReceived, "should receive data”)
}
This is how you would wrap it in a test case. You need the URL. Apple provides an expectation, which you can use to wait for asynchronous tasks. In the closure, we store the data that we receive in a variable, which will have been initialized as an optional of NSData
. After that, there’s a very important line of code which is waitForExpectationsWithTimeout
. This will wait exactly five seconds before the test; the wait will interrupt and fail. You can also do some error code handling within the handler. After that, you can assert that the data you received is not nil
. That’s how you would test asynchronous calls.
Reflection (4:18)
On day one of Swift Summit, we had a lot of very interesting discussions about reflection. Why all the fuss? In my opinion, for testing you will need some important things: you will need mocks, stubs, and fakes — all the things you find in a mocking framework, which will be based on reflection. You use mocking frameworks because it makes the testing environment very easy, very helpful, and very readable. Swift offers some very basic reflection, which isn’t part of the public API (so please don’t use this is your production code!). The main part of this private API is a reflect
method func reflect<T>(x: T) -> MirrorType
which returns a MirrorType
. Both are based off the standard API.
There’s also a protocol called Reflectable:
/// A protocol that produces a reflection interface for a value.
protocol Reflectable {
/// Get the mirror that reflects this object.
func getMirror() -> Mirror
}
protocol MirrorType {
/// Copy the value out as an Any.
var value: Any { get }
/// Get the type of the value.
var valueType: Any.Type { get }
/// Get the number of logical children this value has.
var count: Int { get }
...
}
It’s very basic, but it helps you get the value of an object during runtime. You can iterate over all properties, but not methods, and there’s only a getter, no setter.
Beyond that, I have to say there’s nothing new here, except what we have forgotten, and are rediscovering. Testing is testing. If you do it in Swift, if you do it in Objective-C, if you do it in JavaScript, if you do in Java, you need to have a good knowledge of certain things, and you need to have a certain environment. With Swift, at the moment all we have is XCTest, and only very basic reflection, so we need to take care of our stubs, mocks, and fakes by ourselves.
Lessons Learnt with Production Swift (6:57)
Next, I want to share the lessons I have learned. In our project, we started to use Swift in production only two weeks after it was released. In June 2014! For a client, as of speaking we have nearly 15,000 lines of production code, and the same amount of test code — I would say we are very close to 100% test coverage. How does it feel? It feels fun, really fun. It can also be annoying, but what I’ve learned is that if you do heavy testing in Swift, you are forced to write clean code, so the annoyances are worth it.
It is very easy for a beginner to start testing. Swift makes it really easy, because you can start writing your mocks inline in the methods you are testing. That’s pretty new, and it makes it really easy to try something out. We already had the opportunity to use mocking frameworks with Objective-C, with frameworks like Mockito, adapted for Objective-C by OCMockito. We can use this for Swift too.
@objc
protocol Connection {
func fetchData() -> String
}
class ServerConnection : NSObject, Connection {
func fetchData() -> String {
return "real data returned from other system"
}
}
This is an example of something you might write if you want to mock a server connection. There are some limitations: the tests have to be written in Objective-C, and the objects that you want to mock have to inherit from NSObject
. The references — and this is pretty annoying — have to implement a protocol. And unfortunately, I’m not able to stub, expect, or verify class methods. But with this very simple setup in your Swift classes, you can create mocks like this. You don’t have to write your own stubs, you don’t have to write your own mocks.
- (void)testMockingAnObject
{
id mock = OCMClassMock([ServerConnection class]);
OCMStub([mock fetchData]).andReturn(@"stubbed!");
Controller * controller = [Controller newController];
controller.connection = mock;
[controller redisplay];
OCMVerify([mock fetchData]);
XCTAssertEqualObjects(@"stubbed!", controller.data, @"Excpected stubbed data.");
}
This code only creates stubs. It says “I want a stub,” the method fetches data, and it should return the string stubbed. By doing this, I can have a controller where I just plug in my mock and I call the method. The mock wraps my real implementation, and returns what I expect.
Writing Your Own Mocks and Fakes (10:12)
If you do write mocks and fakes on your own, it’s important to remember that the test setup is the key to success. You should be able to understand the test class within a few seconds, it shouldn’t take any longer. You will have a lot of boilerplate, but that’s why you should go for reusable mocks. You should separate test classes for different use cases. Let’s imagine that you have an HTTP client which is pretty big, with a test class for success cases, and a test class for failure cases, just to split it up. A very important thing is that you have to separate the pure from the impure and stateful — the old lesson about separating the UI from the business code. Go for thin view controllers. Don’t modify external states, and don’t produce side effects, because this makes testing really, really hard.
Compile Times! (12:13)
The other big thing when testing with Swift is compile time, particularly if you have to build for Swift 1.1. Including tests, we may have 30,000 lines of code. It takes us 60 seconds to compile, which is really slow, and especially so if you are using Test Driven Development. It’s a great way to make time to read your emails, or go for a walk.
There are two things I can recommend to you. Firstly, if at all possible, build with Swift 1.2. Apple added the ability to compile with incremental builds, which gives a huge performance boost. Secondly, don’t add your production code as a member of your testing target. If you do so, the production code will be compiled twice, which would take up one-third of compile time.
Frameworks (13:27)
I’d like to mention Hamcrest for Swift. It has some really, really awesome asserts. Very easy to use, especially with optionals. Another thing I’d recommend is UIAutomation
, especially for functional tests. Definitely try that out as part of Xcode.
And to end, a prediction for the future of Swift testing: reflection is coming. Soon, we will receive many compiler improvements, and we will see many new tools.
Thank you very much!
You can view Jan’s example tests on GitHub.
Receive news and updates from Realm straight to your inbox