Improving Unit Testing on iOS Using Frameworks
Introduction
How fast should running your unit tests be? This is an important question, but the answer can vary widely. Perhaps you’re working on a small tool that only a handful of users use, or maybe you’re working on a large enterprise project with millions of users. Obviously the unit testing runtimes are going to be different. That being said, some things are common.
“…test suites should run fast enough that you’re not discouraged from running them frequently enough.” — Martin Fowler
I think this sentiment could apply to all projects, regardless of the type or size. We need a way to ensure running our tests doesn’t become something we avoid. In this post I’d like to show one strategy that gets us going in the right direction.
Demo Project
In order to illustrate this technique, I’d like to start with a simple demo project.
This is a favorite dog name tracker. Specific names can be selected from the main menu. Favorites can be viewed on their own page. Deselecting items from either list will remove them from the favorites page.
Let’s take a look at some of the code for this project.
This is an interface of the StringListLoader
protocol and FileListLoader
which implements it. This type is responsible for pulling our strings from a local static file located in the project. I won’t go into detail on the implementation here because it’s not the focus of the example, but just know that the data fetching is happening here. Let’s take a look at something more interesting.
These are the model types associated with this app. We have a simple, generic Favoritable
type that can work with any type. For this example we’re just using Strings. The FavoriteNamesSource
holds onto a list of these Favoritable
types and provides an interface to list either every item in the list or just the ones that have been favorited. Notice that neither of these types have dependencies on UIKit. Now let’s take a look at some key methods in the main view controller.
The view controller takes the data from these model objects and makes changes to the UI accordingly. It keeps track of whether favorites are selected via its segmented control state and reads this state from the computed property isFavoritesSelected
. When populating the cells of the table in the cellForRow
method, it checks each Favoritable
to see whether it has been favorited and then adds or removes the check mark accessory for the cell based on the results. Tapping on a cell will toggle the favorite status for that model. Finally, we can toggle between which items are populated in the table by tapping on the segmented control.
Now let’s take a look at the unit tests for our model objects:
Notice how these unit tests are just verifying the model logic. There are no UI checks in these tests. There are additional tests for this project, but this is enough to to discuss the general strategy here. We’re able to test that a FavoriteNamesSource
can return a list of favorited items, that it can list all items from the original data set, and that items can be unfavorited after being favorited. Based on the demo at the beginning of the project, I’m sure you’re thinking of others that we could write.
For convenience, I’ve just left all the files in the main project group:
Let’s run these tests and see what the result is.
This build and test took 3.8 seconds. This isn’t the end of the world, but it definitely seems slow for such a small project. And why is the simulator running? We’re not testing an UI code and the tests don’t need the device to verify the models.
Using frameworks
The issue is that our project targets an iOS application. Let’s move our models into a separate project and set the target to an iOS framework.
Now let’s run those tests again.
Conclusion
After making the changes, the build and test took 1.4 seconds, 2.4 seconds faster, and this is just on a small example. There are several additional benefits to splitting out models and tests into separate modules. You’ll have clearer responsibilities in the application, be able to reuse model object across different targets, and have the ability to pull out frameworks to be imported across projects. Also, if you’re looking to practice TDD, having even just small test speed improvements will add up very quickly and you move through your red, green, refactor workflow.
Here’s a link to the project if you’re interested in taking a closer look:
https://github.com/CapTechMobile/iOS-Unit-Testing-With-Frameworks
Also, did you know we’re hiring? If you’re a data engineer or java developer, you’re in especially high demand. Click here to see if we have the right fit for you.