A little while ago I wrote about Cedar, a BDD-style testing framework for Objective-C. The responses I received nearly all went something along these lines: “That’s great! Too bad I can’t use it, since I’m writing an iPhone app.”
I actually wrote Cedar specifically for testing iPhone OS projects we’re working on at Pivotal. To prove it, I’ve started a small public iPhone project that I’ve test-driven entirely with Cedar. You can get the project here (more on that in a bit); it should eventually allow you to log into Pivotal Tracker, see all the delivered stories in a given project, and accept or reject each one. At the moment it does little more than start up and display the Pivotal Chicken*, but it does contain Cedar specs that run on and off the device.
“How is this possible?” you ask. I’ve done two things to make this work:
I separated out all classes that don’t depend on UIKit into a target that builds a static library. The specs for this target run as a console app using the OS X runtime, so no need to worry about runtime support for blocks (assuming you’re running 10.6). Also no need to incur the overhead of starting the emulator every time you run tests. This is a pattern I started using ages ago to make automated testing easier on Win32 client applications, and it works great for all the mobile platforms I’ve worked on. Framework independence means faster tests, and faster tests mean happier programmers. I recommend doing this whether you’re interested in testing with Cedar or not.
Tests for the actual app, which does depend on UIKit and therefore must target the iPhone runtime, run on the emulator (or, in theory, a device) using the PLBlocks iPhone runtime for block support.
Getting the specs up and running takes a few steps, but as we all know good things come to those who wait.
Clone, or otherwise obtain the StoryAccepter project. It will probably have a number of missing library references.
You’ll need to build Cedar, both the dynamic framework (Cedar.framework) and the static iPhone library (libCedar-iPhone.a), as well as the OCHamcrest and OCMock frameworks. Fix the references in the StoryAccepter project to point to these libraries on your system.
If you’re running Leopard you’ll need to install PLBlocks 1.0 for Leopard, and you’ll need to include the runtime and set the compiler for both spec targets; the PLBlocks page has excellent instructions. If you’re running Snow Leopard the project should already contain the runtime, so you’ll just need to download and install the compiler for PLBlocks 1.0.1.
Select the DomainSpec target, and make sure you’ve selected the appropriate Mac OS X runtime for your system. Build and run; you should see dots appear in the console window as the specs run.
Select the StoryAccepterSpec target, and make sure you’ve selected an iPhone runtime (if you want to try running on a device you’ll have to set up the provisioning, of course). Build and run; the emulator should start up and try to run the app, which will simply run the specs and then exit. You should see dots in the console window, as before.
All of this still has some rough spots, especially the UIKit-dependent specs, but even so I’ve found test driving with hierarchical describe blocks far more pleasant than using OCUnit. Some things I hope to improve on:
I imagine the app that runs the UIKit-dependent specs showing a graphical display of test results, perhaps similar to what GHUnit displays when run on the emulator or device.
The iPhone doesn’t allow dynamic libraries, and I haven’t found a way to use OCHamcrest or OCMock for UIKit-dependent specs. The folks at Carbon Five describe using these libraries in their tests on the device; I’m curious to know how they pull that off.
Once iPhone SDK 4.0 comes out with support for blocks this should all work without the need for the PLBlocks runtime. That won’t help iPad development for the foreseeable future, though.
Full-stack integration testing à la Selenium.
Lots of other things. What would you like to see added to Cedar?
*Not an officially endorsed Pivotal mascot (yet).