Shocking! Tips That Might Just Make Testing in Xcode Fun
Techniques to improve the quality of your Xcode testing experience
Writing unit tests for your code is a lot like eating a full serving of vegetables a day. You know it’s good for you—and all the experts say it is the right thing to do—but sometimes there can be all kinds of obstacles in your path.
It’s too expensive! It’s not fun! I ate vegetables/wrote tests yesterday, isn’t that enough?
In order to turn writing tests into a habit, having a good set of tools is essential. When developing for Apple platforms, you can take advantage of many of Xcode’s built-in testing frameworks as a low-friction way to get started. As you get comfortable with these tools, you can start to build on them, and gradually increase the number of tests you write, making them part of your normal development workflow.
That said, once you’ve decided to go all-in on testing, it can be overwhelming. At first, it can feel like you are spending more time, and writing more lines of code, for your tests than for the actual code itself. That may indeed be true! However, putting together good processes for writing tests can make the whole endeavor feel less daunting.
What follows are some tips and tricks, somewhat specific to writing tests with Xcode, that will enable you to tackle adding tests to an existing code base, or implement test-driven development (TDD) in a new project. There are no guarantees, however, that you will start eating more vegetables!
Note: If you still need to be convinced that writing tests early in your development process is a good idea, this article may not change your mind. Instead, read more about testing in many of the books and articles available online. A good starter set is listed in the Resources section below.
Tip #1 — Learn lightning-fast keyboard shortcuts
You may already be familiar with keyboard shortcuts for building and editing in Xcode. However, there are some really handy ones for working with tests as well.
The one that will save you the most time is under Product > Perform Action > Test Again inside Xcode, which by default is tied to ⌃⌥⌘G (Control-Option-Command G). This will run whatever test or test suite you last ran. You don’t need to be viewing the source code for that test, or tap on anything. Just run Test Again, and you’ll immediately re-build and run the last test you explicitly clicked on. This can be a real time-saver when you have switched away from the test to make a change and just want to run the test again for validation.
The rest of the test-related keyboard shortcuts are all, by default, tied to the U key.
- ⌘U (Command-U) — Runs all tests.
- ⇧⌘U (Shift-Command-U) — Build the tests, without running them. This can help track down test compilation errors.
- ⌃⌘U (Control-Command-U) — Runs all tests without building. This is handy if you want to quickly re-run something, perhaps with breakpoints turned on or moved, and you know you haven’t made any source code changes.
Tip #2 — Use code coverage — just a bit
In the “Test” section of the scheme editor, under the “Options” tab, you can enable or disable code coverage.
Enabling code coverage allows Xcode to measure which lines of source code are covered by tests. We’ve found aiming for 100% code coverage is often an unrealistic goal for iOS and macOS projects due to some issues when trying to generate tests for user interface-related code. However, using code coverage to ensure that your unit tests are complete for a particular source file can be very helpful.
Xcode will always show you coverage data for the last test that you ran, whether it was a single test, a test class, or the entire suite. Having your unit test file and source file open side-by-side in the Assistant Editor can be a great way to see how your coverage grows as you add tests, and ensure that you hit as many error cases and branch points as possible. There’s also a feeling of satisfaction as you watch the red bars on your source file start to disappear.
Tip #3 — Create your own test template files
Ideally, you will be writing many unit and integration test cases as you start to ramp up your testing effort. Speed up test creation by spending a little bit of time creating a custom Xcode template that you use to create your tests. The steps to do this depend on your version of Xcode, but the following steps work in Xcode 10.
First, locate a template to start from. For example, the basic Unit Test template file is located here: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Templates/File Templates/Source/Unit Test Case Class.xctemplate
That directory contains the following files:
- TemplateIcon.png
- TemplateIcon@2x.png
- TemplateInfo.plist
- XCTestCaseObjective-C/
- XCTestCaseSwift/
Note: You can customize the icon file if you’d like (48x48px PNG or 96x96px PNG for @2x), but it is not required.
Next, create your own template folder to hold your custom templates. You can name it whatever you’d like, but it should always live under ~/Library/Develop/Xcode/Templates/File Templates/
. Now, the name of the last directory is up to you — it will be displayed in the new file dialog in Xcode as the name of a group containing your custom templates. For example, this will show up as “Custom” in the dialog:
~/Library/Developer/Xcode/Templates/File Templates/Custom/
Then, make a copy of the .xctemplate
from before inside this new folder. This is the copy you will customize.
The last step is to edit the TemplateInfo.plist
file to customize some of the fields. You’ll definitely want to change Description
and Summary
so you’ll be able to tell your templates apart.
Inside the two language directories are the actual templates for the generated files. Feel free to edit as you see fit. Good things to add are @testable import
statements for your app code, deleting unused boilerplate code, or adding custom setup and cleanup code.
Restart Xcode, and you should see your new test template show up in the group using the name you picked—Custom in the example.
Tip #4 — Treat writing tests just like writing code
Just the thought of having to write a whole suite of tests can be daunting. Tests are not necessarily the “fun” part of writing your app. However, opportunities can arise where you are doing more than simple assertion checks in your tests. Try to treat test code as regular code. Refactor test code too! Use a common checker function, or use loops to test a series of cases, instead of writing everything out.
When you start looking at your tests as code that can be optimized and streamlined, it becomes more interesting to work on them. You may find clever ways to test complex conditions in integration tests, come up with ways to initialize shared state, or add interesting error conditions using mocked errors or network stub calls.
One handy trick for reducing the amount of code written is to have a buildSut()
method that returns the object being unit-tested (“Sut” stands for “System Under Test” or “Subject Under Test”, depending on your software background). Having such an object created in one place makes it much simpler to update the tests in the (almost inevitable) case that new arguments are added to the initializer, or additional steps must be taken to properly set up your Sut.
You can also use the debugger inside Xcode when working through failing tests just like you do with app code. Breakpoints in both the test code and your target app will be respected, and you can also add logging statements where necessary.
Logging statements can be an easier way to debug asynchronous or timing-dependent tests. If you use breakpoints along with waiting for expectations, make sure you increase the timeout duration for any wait calls. Often, changing something from a timeout of 1.0
to 10000.0
is a quick-and-easy way to make sure your tests don’t time out because you are sitting in the debugger. Just remember to change them back when you have finished debugging the test!
Wrap-Up
The goal is always to have reliable code and confidence that it can handle both routine functionality and error cases with predictability and correctness. Having a robust test suite in place can be the difference between hoping your code works and knowing that it does.
Using some of the techniques described above should help you move toward that goal with either an existing or new code base. The earlier you can start writing both unit and integration tests for your code, the less onerous it will be in the long term. You will also know that as you refactor in the future, or need to add new cases or behavior, you will have a test suite to verify your changes.
Do you have any favorite tips or tricks to make test-driven development in Xcode easier? Any testing frameworks or libraries you think we need to hear about? Write a response to this article or give us a mention on Twitter!
Resources
Books
- Testing Swift by Paul Hudson — learn how writing better tests helps you build better app
Articles
- Mocking in Swift — Swift by Sundell
- The Testing issue of objc.io—It’s a bit old, but still a great overview!
- XCTestCase /XCTestExpectation / measureBlock() — A basic overview of XCTest