One of the most frustrating things about mobile app development is finding a nasty problem in a just-released version and being forced to pull it off the market. It is this frustration that drove me to explore automated testing at Noom, and specifically unit testing for the Android and iOS platforms.
The goal of automated testing in general, and unit testing in particular, is to let the machines do the heavy lifting by testing for problems in code automatically. Unit testing is focused on testing isolated “units” of codes, without requiring the entire application to be running. This is great for testing complex logic, especially in cases where a developer isn’t 100% confident about some part of their code.
Because Noom applications are available for iOS and Android, unit testing for iOS is just as important for us as it is on Android. While my earlier posts (here and here) discussed the process of setting up unit testing for Android, this post discusses the basics of what it takes to do the same on iOS.
One of the choices you need to make is whether to test on a physical device or the iOS simulator. Because it is easier and faster to run tests on a simulator, I made a choice of not pursuing on-device testing. However, it would be easy to adapt the code below to run on a physical device. Additionally, there are also multiple cloud testing providers (including Amazon) which provide easy access to devices.
Another important point is that unlike Android, where unit tests can run in pure Java without any device or emulator/simulator, in iOS unit tests must run on the iOS simulator. While there are many hacky ways to get around that, all of them have downsides, and for the projects I am working on, the simulator was an easier choice. Of course, using the iOS simulator requires a Mac laptop or desktop, or Mac OS X Server. When executing in a continuous build (CI) environment with tools like Jenkins or Travis, a dedicated Mac “slave” would be needed.
In addition to the normal unit testing support that comes built-in with XCode, I also wanted to have ability to do easy-to-read assertions (similar to what AssertJ provides for Android), and mocking, especially mocking of singletons. I choose to use Expecta for assertions and OCMock for mocking.
Setting Up XCode
Assuming you have an existing XCode project, the first step to take before unit testing is to add a test target. You can do that by opening the project, then going to “File”, “New”, “Target”, select the “OS X Unit Testing Bundle.” You can name it something similar to the project (“ProjectTests”, etc). Screenshot appears below:
The next step is to install the third party libraries (Expecta and OCMock) that will help with writing tests. If you are using CocoaPods or Carthage, this is straightforward. Otherwise, you would need to add them manually to your project (and in case of Expecta, build it first locally). You can follow instructions for installing Expecta and OCMock here and here.
One thing to watch out for is that both libraries produce dynamic frameworks for iOS and static libraries (Carthage only supports dynamic frameworks). Dynamic frameworks are only supported in iOS 8 or later, so if you are using iOS 7 the setup gets trickier. In order to develop on iOS 7, you would need to import the static libraries for each of these (look for the “.a” file), and import the headers into your XCode project.
Running Unit Tests
There are two ways to run unit tests, via XCode and via command line. To run the tests via XCode, you would select the target and click the little run icon next to it. Examples:
You can also build and run unit tests from command line via xcodebuild as follows:
Note that you must set the SDK to iphonesimulator and not ios since the tests need to run inside the simulator. Also, this assumes you have a workspace and a scheme, if you don’t see xcodebuild communication.
To make things more readable, I highly recommend installing xcpretty. You basically pipe the output of xcodebuild through it and it displays it in a better fashion. Another good feature of xcpretty is that it can generate HTML and JUnit XML reports for integration with build systems. Example of the same command with xcpretty, and generating both HTML and JUnit reports:
You can also shut down the simulator from command line once it is done using the following script in XCode 7 / iOS 9:
For earlier versions of XCode, use the following:
In a followup post, I hope to discuss the nuts and bolts of writing unit tests for iOS.