Swift & Nimble Testing

I’m a big fan of unit testing and when I discovered the BDD framework Nimble with Swift support a couple of months ago I was delighted. One of its advantages is that instead of using the XCTAssertEqual macros you can write:

expect(answer).to.equal(42)

which, thanks to Swift’s support for operator overloading, can be made even more expressive:

expect(answer) == 42

However, there was one area where I found the syntax a bit verbose – when comparing floating point numbers:

expect(answer).to(beCloseTo(42.0))

What this does is simply make the comparison fuzzy by allowing a difference of 0.0001 – the default delta as defined by the framework.

It would be great if we could write it this way:

expect(answer) ≈ 42.0    // type Option-x for ≈ (U.S. keyboard)

And actually we can, thanks to Swift’s support for custom operators. Now understandably people worry about misuse of custom operators and I fully agree that you need to be very careful when and where you use them. But I feel that test code is a good place where a readability ‘optimisation’ like this one can be applied.

All unit tests do is compare expectations to actuals and anything we can do to make this concise and readable for the actual values stand out over the boilerplate is a win. Custom operators are a good tool for this, especially if they mirror universal symbols like the mathematical sign of inequality.

Justifications aside, how does this work?

Nimble allows you to define so-called custom matchers that extend the set of validations:

public func equal(expectedValue: T?) -> MatcherFunc {
  return MatcherFunc { actualExpression, failureMessage in
    failureMessage.postfixMessage = "equal <(expectedValue)>"
    return actualExpression.evaluate() == expectedValue
  }
}

The existing package already provides a beCloseTo matcher for decimal number comparisons and it is then straightforward to define an operator for it:

infix operator ≈ {}
public func ≈(lhs: Expectation, rhs: Double) {
    lhs.to(beCloseTo(rhs))
}

But what’s missing here is the case where you specify a delta different from the default:

expect(answer).to(beCloseTo(42.0, within: 1.0)

In other words if we want to specify the delta (and that’s probably quite common) we’re back to the more verbose version. Ideally we’d like to write this as:

expect(answer) == 42.0 ± 1.0    // type Option-Shift-= for ± (U.S. keyboard)

Turns out we can, and the way this works is as follows. First we create a binary operator ± that converts the value to its left and the delta to its right into a tuple (expected: Double, delta: Double):

infix operator ± { precedence 170 }
public func ±(lhs: Double, rhs: Double) -> (expected: Double, delta: Double) {
    return (expected: lhs, delta: rhs)
}

Then we add an overloaded method which takes an Expectation<Double> and the tuple (expected: Double, delta: Double) as parameters:

public func ≈(lhs: Expectation, rhs: (expected: Double, delta: Double)) {
    lhs.to(beCloseTo(rhs.expected, within: rhs.delta))
}

These changes have been kindly accepted and integrated by the Nimble team into the framework as of Jan 5 (commit e7bafdb)

Part of this change was also an extension for comparisons of arrays of numbers:

expect([0.0, 1.1, 2.2]) ≈ [0.0001, 1.1001, 2.2001]
expect([0.0, 1.1, 2.2]).to(beCloseTo([0.1, 1.2, 2.3], within: 0.1))

See the Nimble documentation for further examples.

SpriteKit and Swift – converting a project from Objective-C

In November 2013 I was looking for an excuse to play with SpriteKit and came up with the idea for a mini-game with a Christmas theme. This is what it looks like:

When Swift was announced at WWDC 2014 I was eager to give it a try and looked through my grab bag of side projects for a suitable candidate to try migrating a project from Objective-C to Swift. ’Shooter‘ was a good candidate, because it‘s not too big but also not too trivial.

The source code is available on github and I‘m planning to post about lessons learned when transitioning from Objective-C to Swift in a future update. However, the commit history already tells a pretty decent story of the transition. Commit ab6df2e merges the ‘swift’ branch into master and 8ed7782 is where the journey starts.

Switfly build a Mac app

Curious about Swift, I went ahead and translated Matt Gallagher‘s example from Objective-C to Swift. If you stick this in a Playground file it will launch a minimal Mac app. Or you create a simple text file and chmod +x it for direct execution from the command line.

#! /usr/bin/swift -sdk /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.10.sdk

import Cocoa

var app = NSApplication.sharedApplication()
app.setActivationPolicy(.Regular)

var menuBar = NSMenu()
var appMenuItem = NSMenuItem()
menuBar.addItem(appMenuItem)
app.mainMenu = menuBar

var appMenu = NSMenu()
var appName = NSProcessInfo.processInfo().processName
var quitTitle = "Quit \(appName)"
var quitMenuItem = NSMenuItem(
    title: quitTitle,
    action: Selector("terminate:"),
    keyEquivalent: "q"
)
appMenu.addItem(quitMenuItem)

appMenuItem.submenu = appMenu

var window = NSWindow(
    contentRect: CGRect(x: 0, y: 0, width: 200, height: 200),
    styleMask: NSTitledWindowMask,
    backing: NSBackingStoreType.Buffered,
    defer: false
)

window.cascadeTopLeftFromPoint(NSPoint(x: 20, y: 20))
window.title = appName
window.makeKeyAndOrderFront(nil)
app.activateIgnoringOtherApps(true)
app.run()

Location-aware passcode settings

Are you on the fence what the passcode setting should be on your iOS device? Are you worried that anyone could access all your data if you left it somewhere yet at the same time annoyed by having to type in your passcode all the time?

I know I am. I use my phone so frequently that a five minute passcode setting actually rarely kicks in. My iPad on the other hand I don’t pick up as often and therefore I would have to enter the passcode pretty much every time. Which is why I don’t use one.

This came back to bite me a few weeks ago when I left my iPad on the cross-trainer in the gym. When I noticed my iPad was missing I wished I had at least set a passcode to protect its contents. Thinking about why I hadn't, it occurred to me how I could have had the best of both worlds: no passcode while at home or at other places I consider “safe” and a passcode everywhere else.

iOS should simply allow you to define location specific passcode settings. You should be able to define a default which applies everywhere and then one or more regions that are exceptions. This would allow you to define a basic safe setting with a passcode and then other areas where you’re more liberal. You could even have passcode settings of different complexity depending on where you are. No passcode at your home, a complex one abroad, and a simple one everywhere else.

Below you‘ll find an illustration of what the setup for this could look like.