Code Coverage in Xcode - How to avoid a vanity metric for your iOS app
Since Xcode 7, iOS developers can generate a code coverage for their app: a report showing which area of their app is covered by unit tests. However, this is isn’t always accurate, let’s see why you should not base your code health only on code coverage.
First thing first, let’s see how to gather code coverage. In Xcode, edit the scheme of your application. Under Test > Options, make sure select _“Gather coverage for all targets”. Today, I want to see only my app, so I’ll select a specific one.
The next time you’ll run your unit tests, you’ll that you’ve got a report of code coverage showing how much of you app is covered.
That’s where the issue showed up.
If you look closer to your report, you can see your
ViewController are already quit covered, where there are no tests for it.
This happen because your testing target uses your app as Host Application.
It has to launch the application to run the tests, so any code executed during the launch of the application would automatically be flagged as covered.
So how to avoid this?
Well, I thought one way to tackle this is to disable Host Application. Although that means I need to add each file I want to to the testing target to make it work.
The application doesn’t need to be installed or launched anymore to run the tests which make the execution quite faster.
However, even if it can fit for app helper or small iOS project, it doesn’t seems right to add each file to test to the unit test target.
I also noticed sometimes the report can be inconsistent and doesn’t always generate the expected coverage: I had issues with Xcode 10.2 where coverage stick to 0%.
Back to square one, my second idea was to use a different approach, using a framework this time.
By separating logic and keeping the business logic into a separated framework, I can run the associated unit tests and gather the coverage the same way.
Turns out, this approach gave me the most consistent result: I have a complete code coverage of my framework and still can use it in the main application with a classic import.
It also keep a clean separation of concern between business logic and user interface. If I have to add unit tests or UI tests for my views, it would make sense to add them under the main test target.
In conclusion, if code coverage can be a useful hint for a healthy code, be careful to the usage. Remember that an app with 100% code coverage doesn’t mean it’s perfect.
On my side, I mostly use code coverage evolution between two releases to see how tend the development:
- 📈if it goes up, it means we either removed untested code or added tests to it.
- 📉if it goes down, it most likely we added code not tested. We rarely remove unit tests.
Thanks for reading!