As I mentioned in my Getting Testy series, I typically practice a form of outside-in testing, or as Justin Searls has been calling it, Discovery Testing.

Often, I end up with small, simple, reusable classes that I can use as building blocks.

I recently worked on an iOS project in Swift where some of these building blocks emerged in the course of test-driving the application.

Periodic Tasks

One feature of our application needed to repeatedly run a task at some interval. This can be done with NSTimer, but we wanted a simpler interface in order to make testing easier. We used our tests to derive a PeriodicTaskLauncher protocol with the interface we wanted.

PeriodicTaskLauncher
typealias PeriodicTask = () -> Void
protocol PeriodicTaskLauncher {
func every(interval: NSTimeInterval, launch task: PeriodicTask)
var active: Bool { get }
func start()
func stop()
}

We also implemented a FakePeriodicTaskLauncher test double for testing. Eli Perkins has a very nice introduction to this technique.

Once we finished developing the code that uses PeriodicTaskLauncher, we were able to move one level in and test-drive the default task launcher implementation.

DefaultPeriodicTaskLauncher
class DefaultPeriodicTaskLauncher: NSObject, PeriodicTaskLauncher {
private var interval: NSTimeInterval?
private var task: PeriodicTask?
private var timer: NSTimer?
var active: Bool {
return timer != nil
}
func every(interval: NSTimeInterval, launch task: PeriodicTask) {
self.interval = interval
self.task = task
}
func start() {
guard let interval = interval else { return }
timer = NSTimer.scheduledTimerWithTimeInterval(interval, target: self, selector: "timerTick:", userInfo: nil, repeats: true)
}
func stop() {
guard let activeTimer = timer else { return }
activeTimer.invalidate()
timer = nil
}
func timerTick(timer: NSTimer) {
task?()
}
}

Rate Limiting

Later in the same application, we had a situation where we needed to perform a task in response to user interaction, but we needed to limit how often we’d perform the task. To solve this problem, we built a RateLimiter class.

RateLimiter
class RateLimiter {
typealias Action = () -> Void
let taskLauncher: PeriodicTaskLauncher
private var lastAction: Action?
init(rate: NSTimeInterval, taskLauncher: PeriodicTaskLauncher = DefaultPeriodicTaskLauncher()) {
self.taskLauncher = taskLauncher
self.taskLauncher.every(rate) { self.executeLastAction() }
}
func execute(action: Action) {
self.lastAction = action
guard isQuiescent else { return }
executeLastAction()
taskLauncher.start()
}
private var isQuiescent: Bool {
return !taskLauncher.active
}
private func executeLastAction() {
if let action = lastAction {
action()
lastAction = nil
} else {
taskLauncher.stop()
}
}
}

RateLimiter starts in a quiescent state. In that state, it executes any incoming action immediately and then starts its task launcher. In that state, it hangs onto any incoming actions. If more actions come in before the task launcher runs, the previous action is discarded and the new action is remembered. When the task launcher runs, it executes the remembered action. If there is no action to execute, the task launcher is stopped, putting the RateLimiter back into the quiescent state.

We use RateLimiter like this:

Use RateLimiter
let rateLimiter = RateLimiter(rate: 0.25)
func onSomeUserAction() {
rateLimiter.execute() { self.performMyAction() }
}

Note that RateLimiter makes use of PeriodicTaskLauncher internally.

Conclusion

When I practice outside-in testing, I often end up with these small, simple, single-purpose classes that can then be used in other contexts. They form reusable building blocks that can be used throughout the application very easily.

I find that this testing discipline naturally results in classes that follow the Single Responsibility Principle and are naturally reusable in other contexts as with the PeriodicTaskLauncher here.