Test-Driven iOS Development: Book Review 

 November 12, 2012

by Jon Reid


Many programmers assume that test-driven development (TDD) doesn’t work well for iOS development. This ill-founded assumption really comes from a lack of any experience with TDD. But because iOS developers learn most of their chops by referring to other people’s code, it also comes from a lack of helpful examples.

Disclosure: The book links below are affiliate links. If you buy anything, I earn a commission, at no extra cost to you.

So I was really glad when Graham Lee’s book Test-Driven iOS Development came out. Finally, something I can point others to besides my code!

Book Overview

Chapter 1 starts off with a surprising answer to the question, “Why write unit tests?” Why, to make more money! It shows how the traditional handoff-to-QA form of testing comes from the waterfall model of development, and how the cost of fixing defects increases with each phase of the waterfall.

Chapter 2 lays out the principles of test-driven development: test first, writing “Just Barely Good Enough” code that satisfies the test, and refactoring. But it also describes an important principle from Extreme Programming: “Ya Ain’t Gonna Need It,” or YAGNI. Basically, code only what you need to. This keeps production code simple, and avoids wasting time writing code that won’t have any effect.

The book goes on to introduce unit testing by showing how one might write tests without a unit testing framework—the old-fashioned way! Then we get an overview of the more common tools. Finally, we hit the meat of the book: a full example of creating an iOS app using TDD, spanning five chapters.

My A-ha! Moments

The big example not only demonstrates TDD, but also shows how the Single Responsibility Principle improves design. I had two big a-ha! moments reading this code:

  1. All model code I’ve seen includes code to build that model from XML or JSON data. Graham’s code demonstrates the power of separating model construction from representation. This is a big deal not just for improving cohesion. I’ve run into a model that assumes it’s built from XML, only to find a case where it might be built from JSON instead.
  2. All table code I’ve seen takes the table data source code and the table delegate code, and lumps it all together with the overall view controller. This is normal even when the view controller isn’t a UITableViewController. Graham’s code goes against the flow by pulling data source and delegate into a separate class. This allows you to use same view controller for different tables.

Where I Differ

I do want to point out one thing in the book that is incorrect, and another where I disagree.

Unit Test Bundles Should Link Just Fine

After writing the very first test, there’s a need to create the Topic class. On p.68 it says,

Add a new NSObject class called Topic to the project. When Xcode asks which targets to add the new file to, check both the app target and the test bundle target. (This is required to work around a problem with unit test bundles: They don’t link symbols from their host apps correctly.)

That was once true for iOS development with Xcode 3. But with Xcode 4, Apple brought iOS unit testing in line with Mac unit testing: Xcode links the test bundle against the app, and injects it into the running app. If the test bundle is unable to link, there’s a problem in the build settings. Make sure “Symbols Hidden by Default” is “No” for your app’s Debug configuration.

Update: Graham Lee says, “Sadly, at the time of publication, the linker problem still needed to be worked around.”

Is Method Swizzling Worth It?

The part where I disagree is the author’s use of method swizzling. He acknowledges that some may consider method swizzling “too clever,” and in my case, that may simply be because I haven’t used it yet. But let’s look at the two cases where he felt the need for swizzling.

First, on p.151, he’s trying to come up with a way to TDD that viewDidAppear: call its superclass. For me though, this feels like overkill because the “invoke super” idiom is well-ingrained in iOS developers. In particular, I don’t want to make testing look overly convoluted to my coworkers, whom I am trying to encourage to write tests. If I need to soften strict TDD to get them to adopt it at all, I’ll do so.

Still, I do have to say I’ve wondered how to enforce “invoke super” from a test. For idiomatic methods like viewDidAppear: I don’t think this is a big deal. But I may run into another method where it’s less obvious, and needs a test to verify it. Then I’ll know to pull method swizzling out of my toolbox.

The other case where the book uses method swizzling is on pp.154-156. The swizzling replaces a method normally invoked by a notification with a category method that records the notification. The tests verify that the class is observing a notification. But Michael Feathers’ book Working Effectively with Legacy Code shows a different way to replace methods: Subclass and Override.

  • In the test code, create a special subclass that is only for testing. (For a class named Foo, I create a subclass named TestableFoo.)
  • Write your test to use the testing subclass.
  • Override the method.

It’s that easy! And this approach works in any object-oriented language—no swizzling required.

Update: Since writing this, I got into a situation where I needed to write a unit test for code that is tied to a singleton. Can you guess how I altered the singleton for testing? That’s right, I copied Graham’s method swizzling technique straight out of the book! I’d still consider it an unusual tool to be avoided if possible, but method swizzling is definitely part of my toolkit now.

Update 2: OK, I’ve had to use Graham’s technique more than once now. I eat humble pie. As Graham told me in a tweet, “everyone says that’s a gross hack until the moment it solves their problem :-)”

Conclusion: Test-Driven iOS Development Is Worthy

The book concludes with an overview of coding principles that emerge from TDD, including:

  • Design to Interfaces, Not Implementations. This is more important for hand-rolled mocks, because it lets you easily substitute fakes. I find it less important when using mock object frameworks like OCMockito, because the generated mock is pretty close to a perfect stand-in. But designing to protocols isn’t just about substituting fakes; it’s also an important way to break dependencies between classes.
  • Tell, Don’t Ask. This is a powerful tool for TDD, with a number of different ways to approach it. I use constructor injection, setter injection, injecting a factory, or factory methods which I subclass-and-override. It all depends, and I sometimes change my mind and switch.
  • Prefer a Wide, Shallow Inheritance Hierarchy. I’d extend that beyond inheritance, to all dependencies: prefer a wide, shallow dependency graph. Again, protocols make this possible.

I give Test-Driven iOS Development a big thumbs-up. It fills an important hole in TDD examples, and shows how improved designs emerge from good TDD. Pick up a copy today… but don’t just read it! The act of typing someone else’s code actually reinforces the concepts in your head. And these are concepts that will serve you well.

Question: After working through the book, where is it still difficult to apply TDD to your iOS development? Leave a comment below.

Jon Reid

About the author

Programming was fun when I was a kid. But working in Silicon Valley, I saw poor code lead to fear, with real human costs. Looking for ways to make my life better, I learned about Extreme Programming, including unit testing, test-driven development (TDD), and refactoring. Programming became fun again! I've now been doing TDD in Apple environments for 20 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy. Now a coach with Industrial Logic!

  • I’ve been working my way through this book and it’s been great for getting me started with TDD in iOS apps. I have one major complaint, though. For a book like this, I like to enter all of the example code as I go along so that I can make sure it works as stated (nothing broken by Xcode/framework changes since publication) and play around with it to enhance my understanding.

    Very early in the book, however, the author decides to start “saving trees” by omitting certain code, such as interface files, that the reader can figure out on their own. This makes entering the code a chore since there’s a lot of reverse engineering required. Other times code changes are implied within text and easily missed with the assumption that the author is saying, “Here’s what we’re going to do,” to be followed with the actual code which never comes. The code from the book is available for download, but it represents the final state at the end of the project, which isn’t helpful for building it a step at a time.

    Because of those issues, I gave up maintaining the code mid-way through the project. I still recommend the book to anyone who wants to get started with TDD in Xcode, just with that caveat. I’m at a point now where I’m applying the controller testing ideas from the book to a project of my own and am at the point of deciding between swizzling, subclass and override, etc.

    • David,
      That’s a good critique. Why don’t you send a note to the author or the publisher, and ask that if they do a second edition, that they also provide a set of online sources?

      I’m playing with the idea of TDDing the app my own way, and putting it on GitHub so that people can see a fully-formed example.

      • Jon,
        If your TDD write up is ready, please upload the Github link (wanna see some good working code with TDD). I went through your video on youtube with networking TDD (simply loved it). I would really like to see your implementation on UI TDD along with facade also. I would suggest you to go for a book of yours on TDD (a definite buy). If you have one already, do let us know.

  • I’m planning to write something up once I’ve finished with the book. I’m currently at the point I get to in many books where I’ve read enough to start applying the lessons to my own project and want to do so while it’s still fresh in my mind. I had an idea for an app that I was saving specifically to be done in conjunction with this book.

    Up next in the book is a chapter on retroactively applying TDD to an existing project and I have another, larger project that I’ll by applying those lessons to.

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}