How to Rearrange Xcode Projects to Increase Testing 

 February 10, 2015

by Jon Reid


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]

The Problem

Your Xcode project navigator probably looks something like this:

Test code group visible below collapsed production code

(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:

Test code group hidden below expanded production code

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.

Someone opening your project may not even be aware that any tests exist.

Click to Tweet

The Solution

My solution is simple: I eliminate that folder.

In the MarvelBrowser project, I made the following two commits:

  1. In MarvelBrowserTests under “Supporting Files”, I renamed Info.plist to Info-Tests.plist. In the test project, I fixed up the “Info.plist File” build setting. Build and run tests to confirm. (Commit bd7ac52, Rename test target Info.plist to Info-Tests.plist.)
  2. I moved everything in MarvelBrowserTests over to the MarvelBrowser folder. Again, this required fixing up the test target’s “Info.plist File” setting, this time to change the path. Build and run tests to confirm. (Commit 2cd3d8b, Move files from MarvelBrowserTests to MarvelBrowser.)

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.

Four Benefits (and One Drawback)

Here’s part of a different app I made:

Test code intermingled with production code

What are the benefits of arranging an Xcode project in this way?

1. Easier to Find Corresponding Test Code

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.

2. Invites Others to Look at Test Code

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.)

3. Provides Examples of How to Test

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.”

4. Easier to See if There Is Test Code

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.

5. Potential Drawback: When Not to Intermingle

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.

Tips for Rearranging

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.

  1. In your test folder under “Supporting Files”, rename Info.plist to Info-Tests.plist. In the test project, fix up the “Info.plist File” build setting. Build and run tests to confirm.
  2. Move everything in your tests folder over to your main folder. Fix up the test target’s “Info.plist File” setting, this time to change the path. Build and run tests to confirm.

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]

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 love reading your posts, Jon. They often inspire me to try new ideas in unit testing. I often agree with you on most posts too. On this one, however, I have a very different opinion-

    I really *don’t* want my test code to be intermingled with my production code.

    There are several reasons why this may not be a good idea (besides the one you mentioned):

    (1) I expect tests files/groups to mirror production files/groups, much akin to double-ledge accounting.

    That is, I expect a one-to-one relationship between production files/groups and test files/groups.

    If I were to ask “where is my login code?” I would expect to find a “Login” group amongst my production code and a corresponding “Login” group amongst my test code.

    All of my files and groups are alphabetized too, so it’s really easy to find see this pairing.

    This also makes it immediately obvious what has and hasn’t been unit tested.

    Yes, ideally all code would be written following TDD, but in real, “legacy” code bases, this isn’t always true.

    If test files/groups were intermingled with production files/groups, it would be much more difficult to determine what has or hasn’t been tested.

    (2) Before writing new code, I always ask myself, “Is it likely that I will need to reuse this across projects?” Frequently, the answer is “yes” or “possibly.” (I’m lazy after all; I only want to write code once.)

    If there is an immediate possibility to use the code across two or more projects, I will immediately create the code as a separate library/CocoaPod.

    If there is at least a good possibility, I will take extra effort to encapsulate my new code, depend on protocols instead of concrete instances, etc. Basically, I’d really make sure I’m following the open-closed, interface separation, and dependency inversion principles in particular.

    Should my intuition prove to be right, this will make it easier to pull out the code when the need arises; this would include both production and unit test code.

    If the production and unit test code were intermingled, I would have the same issue you mentioned– I would need to sift through and determine which classes belong to which target.

    (3) This basically ensures you’ll be fighting with Xcode.

    As far as I can tell, Xcode seems to use the group to determine which target to add a new file to.

    If you try adding a new unit test file to a production group, Xcode will likely select the production target.

    Eventually, probably due to fatigue, one of these files is going to get added to the wrong target. You may even write a couple tests before getting any warnings/errors.

    For experienced developers, this isn’t a big deal. We know strange error messages (e.g. “XCTest/XCTest.h file not found”) are a clue that the file is part of the wrong target.

    For new developers, however, I imagine these error messages can be very confusing.

    All in all, while there are indeed benefits to the approach you outlined, I find for me at least the cons outweigh the benefits.

    – Joshua

    • Joshua, thanks for your detailed description. I’ll try to briefly respond to each point.

      1. Funny, this is very close to the reason I give for intermingling! Having a group with a parallel-group-in-a-different-parent is certainly the prevailing norm. It’s my goal to show test code simultaneously, rather than making someone navigate to it.
      2. Sifting isn’t always that bad; I filter by Tests.m, multi-select, and can retarget in batches. But you’re right, for internal libraries/pods, I currently don’t intermingle. (Way to go breaking things apart, by the way!)
      3. This is trivial. I’d much rather put test code front-and-center — especially in front of new developers, or developers new to the code.

      …So it’s #2 that gives me pause. I wish we had a way to annotate code.

  • Hello Jon, thank you for this nice article.

    As Joshua, we prefer keep Tests separated from “production” code. With Jetbrains, when testing with a langage supporting packages, the Package view can regroup files, even if they are in same folder.

    Within Xcode, you can use the Symbol navigator to achieve this, (but you lose the groups).

    For the shortcuts, you can use the shortcut for manipulating the file gutter :
    but it’s also not really easy.

    Many we can write a simple plugin to achieve this : ctrl cmd left/right to jump from Test file to header/implementation files, as they are redundant with ctrl cmd up/down.

  • Hi Jon,

    Thanks for yet another great article – keep ’em coming. It may be a bit of topic as this comment isn’t related to Xcode. I tend to use AppCode for my iOS/OSX development – and the Cmd+Shift+T combination comes in handy as it toggles between code and the corresponding test code (and I can keep my test code separated as I prefer).

    – Jens

  • Jon,

    We’ve using this approach for about year and a half now and we believe it’s way better than having separate folder with only tests there.

    First of all this increases tests discoverability. It’s just easier to find whether given class has a spec file and avoid creating a new. Moreover it’s just quicker to read how a given object should behave as you have all the specs in a file below your .m.

    Second of all this fixes really annoying issue with having tests and source separate: the fact that you have to constantly fix specs location. At Taptera we really value refactoring and constantly improving code. This also means our code constantly changes and classes get extracted, renamed, moved and so on. Before we switched to intermingling tests with implementation we had a huge issue with the that tests hierarchy was out of date and did not correspond to actual app folder hierarchy, thus crippling discoverability.

    Last of all it’s just faster. You don’t have to think “where are my tests?”/”Do I have my tests?”/”Are my tests in the right place?”. It’s just right there, below your .m file.

    And commenting a bit on the “Jump to tests” action in AppCode: even though I love AppCode and I can’t imagine working without it I do have to say that this feature is not reliable and it does not always work for me. However I do work on huge codebase and amount of specs files might be a bit confusing for the IDE ;)

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