So you’ve tried to increase iOS unit testing in your organization.
Maybe you tried to lead by example, hoping others on your team would follow suit. Or maybe you tried to increase your own testing.
Either way, your efforts fell flat. Your new emphasis on testing was barely noticed, and eventually forgotten. Leading by example is key to introducing a test-driven culture to your organization. But it’s no good if no one sees your example.
If only there were a way to bring your tests in from the shadows, placing them front & center.
There is. It’s no silver bullet, but it’ll help. And it’s easier than you may think.
[This post is part of the series TDD Sample App: The Complete Collection …So Far]
Your Xcode project navigator probably looks something like this:
(If you’re curious about that XcodeWarnings.xcconfig, see Xcode Warnings: Can You Turn Them Up to Eleven?.)
When you create a new project, Xcode sets up a test target. Apple hooks it all up for you, with a group/folder that looks like a good home for test code. In the case of my MarvelBrowser project, you can see a group MarvelBrowserTests, right under the group for production code.
There’s a problem: we never work like this. We’re writing production code, so guess what? We’ll always expand the production code group to show its contents:
MarvelBrowserTests is getting pushed down by the expanded production code group. And I haven’t even added any code! Start adding a few files, and you won’t even see MarvelBrowserTests anymore.
Out of sight, out of mind. Someone opening your project may not even be aware that any tests exist.
That unhelpful arrangement for test code is similar to what I described in Is Apple Stuffing Your Code with Pointless Cruft?: Apple has decided to make things easier with a template. Unfortunately, the template leads us in the wrong direction.
My solution is simple: I eliminate that folder.
In the MarvelBrowser project, I made the following two commits:
Now I can safely delete the MarvelBrowserTests group and folder. Going forward, production code and test code will be intermingled.
You do need to take a little more care when adding a new file, because you have to pay attention to specify the correct target. That’s a small price to pay for what you get.
Here’s part of a different app I made:
What are the benefits of arranging an Xcode project in this way?
Can you find the tests for ANJStarRatingImageView? …Easy, right?
In both Xcode and AppCode, there’s a keyboard shortcut to jump between a header and its implementation, between Foo.h and Foo.m. But there’s no shortcut to jump to a corresponding test file. So, let’s just keep them as neighbors in the project navigator.
To someone looking at that section of the project for the first time, the test files in the list are an invitation: “Look at me!”
Someone who wants to understand the semantics of the class can read the tests to see what the class does. (When writing tests, I keep tests-as-documentation as a goal in the back of my head.)
Developers new to testing often don’t know where to start. It’s natural to look at existing tests as examples. “How did you write a test for that?”
As you write tests, you also write test support code. These can include fake objects for your particular domain. They can also include testing idioms, like a way to write testable multi-threaded code. Test support code helps others get over the hump of “I want to test this, but it’ll take too long to figure out how.”
It’s pretty obvious to anyone looking at this project that there are tests for ANJStarRatingImageView and ANJCommentCell. A natural question to ask is, “Where are the tests for ANJRatingCell?”
ANJRatingCell is either missing tests, or it’s thoroughly covered by other tests and doesn’t need tests of its own. There’s a big difference between the two, so which is it? Code coverage can answer that question, showing you where the holes lie.
There is one case where I don’t intermingle production code with its test code. That’s when someone else will take my source files but use their own project file. In that case, it’s painful to have to walk through the source files and say, “This one goes in the app target, this one goes in the test target.” It’s easier to assign entire folders instead.
Unfortunately, this applies to anything distributed via CocoaPods, or any system that automatically assembles source files into a subproject. Though perhaps you can find a way to specify, “All
*Tests.m files are test code, along with everything in this TestSupport folder. Everything else is production code.” Then you’d have the best of both worlds.
Since groups and files in the project navigator doesn’t have to match the file system arrangement, you could simply drag your test files to new positions in the project navigator, without moving them in the file system. But there’s a more thorough way that is also quicker.
I think it’s important to change move them in the file system as well. Why? Because not everyone opens the project to look at your sources. Many times, people will go directly to the repository through its web interface. (By the way, this is one reason to prefer spaces over tabs for indenting code.)
So I find it helpful to move test code in the file system first. Assuming your project arrangement isn’t unusual, the quickest way to get things ordered in the Xcode project navigator is to remove everything, then add it back all it once. (This assumes your test file names share the same prefixes as their production counterparts.)
Again, here are the steps to take. Before you start, make sure your existing tests run cleanly. Note the total number of tests.
Finally, confirm that you still have the same number of tests running. Then pat yourself on the back: you’ve brought your tests out of the shadows!
By giving test code equal status with production code, everyone who works on your project will start to think more about testing.
Give it a try! How does it change the feeling of your Xcode project? You can share a comment by clicking here.[This post is part of the series TDD Sample App: The Complete Collection …So Far]
Please log in again. The login page will open in a new window. After logging in you can close it and return to this page.