Many programmers assume that Test-Driven Development 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!
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.
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:
I do want to point out one thing in the book that is incorrect, and another where I disagree.
After writing the very first test, there’s a need to create the
Topicclass. On p.68 it says,
Add a new
Topicto 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.”
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.
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 :-)”
The book concludes with an overview of coding principles that emerge from TDD, including:
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.
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 Design Patterns, Refactoring, and Test-Driven Development (TDD). Programming became fun again! I've now been doing TDD in Apple environments for 18 years. I'm committed to software crafting as a discipline, hoping we can all reach greater effectiveness and joy.
Please log in again. The login page will open in a new tab. After logging in you can close it and return to this page.