How We Ensure Long-Term Quality for iOS Apps (Series 2/3)
Introduction
This article is the second of three in the miniseries from the LumApps iOS team, in which we discuss the quality and maintainability of our mobile application.
This one will focus on our continuous integration process.
If you missed it, have a look at the first article written by Thomas Lombard: How We Ensure Long-Term Quality for iOS Apps (Series 1/3)
Technical stack
Circle CI
Circle CI has been a serious competitor when choosing our continuous integration solution. It had to meet with specific criteria:
- Easy to maintain over time
- OS X hardware for iOS builds
- Compatible with the rest of the projects (android / front web / back end)
- Cloud-based
Circle CI has been an obvious choice, especially via its GitHub integrations.
Fastlane
As we prefer to spend time on code quality rather than performing redundant tasks, we integrated Fastlane to the project.
Fastlane is an open source tool written in ruby developed in order to simplify iOS apps deployment and automate builds & tests workflows.
Here is the non-exhaustive list of our lanes:
take_screenshots
: Generate new localized screenshotsupload_metadata
: Upload the screenshots and metadata to AppStore Connectbuild_release
: Generate the IPA on release configurationupload_binary
: Upload the IPA to AppStore Connectbuild_mock_target
: Build target containing data mockslint
: Run linter and generate a report fileupdate_localisables
: Update localized strings filestests_and_coverage
: Run tests and generate coverage and tests reportsui_tests
: Run UI tests
Some of these lanes are technical shortcuts, others are intended to avoid human errors or to be faster in our tasks.
The actions made by Fastlane are the same on Circle and in our local environment but the outputs generated can be different. For exemple, we never send a Slack message when we run on local, but we show a notification in the notification center.
Lokalise
To keep our localisables up to date, we use Lokalise.
It allows us to manage and generate translation files in the correct format for Xcode.
Technical prevention
We’ve created two workflows to avoid any technical regression during the development
Build and test
In this workflow, we want to send a Slack summary of the project to check some of these requirements:
- Number of warnings (build/lint)
- Check that all targets compile
- Check that all the unit tests pass
- Get coverage rate and trend
It’s triggered by every pull request created and merged on git dev
branch.
Called lanes
fastlane lint
fastlane build_mock_target
fastlane tests_and_coverage
Few words about tests_and_coverage
lane.
This lane uses slather to generate a coverage report. We use slather to ignore some directories that are irrelevant, to be excluded from code coverage (typically view controllers, constants…).
Also, in Circle, SwiftLint is not installed on built-in images, we had to use a custom one to get it working.
To get the report generated by SwiftLint we use Circle workflow.
Finally, to calculate code coverage trend, each report is stored, and can be retrieved through Circle API to be compared afterward.
Nightly tests
This workflow runs the UI tests on each targeted device. It sends a Slack summary of the time used to run the UI tests.
As you can see, UI tests can be longer. It’s the reason we execute these tests only one time per day and during the night.
It checks that we do not have a UI regression and also ensures that our critical path is still bug-free on multiple devices. And could also in the future check different OS versions
This workflow is triggered every evening of the week if there have been modifications in the past 24 hours.
Called lanes
fastlane ui_tests
Release automation
For each release, we use two separate workflows to optimize build times. Thus the last workflow, which can take longer, will only be executed once the version has been validated internally.
Internal testing
This workflow ensures that all the requirements of the “Build and test” and “Nightly tests” workflows are still good.
In parallel, it updates the localisable files of the project. If there’s some modified files, the workflow fails because the project is missing the latest translations!
If all succeeds, it generates an AppStore IPA, and asks for a developer approval. If the IPA is satisfactory, then the developer (release manager) validates the IPA which will be submitted to the internal test teams through TestFlight.
This workflow is triggered by every push on the internal_testing
branch.
Called lanes
update_localisables
fastlane build_release
fastlane upload_binary
Release workflow
When the build is validated by the test team, it’s time to release! 🎉
So, with the validated version we use Fastlane to generate all the screenshots in every supported language for each targeted device.
After that, it sends a Slack message with a package of screenshots, a button to validate the workflow and a link to Circle CI artifacts that contain an HTML that summarizes all the generated screenshots.
When the workflow is validated, the screenshots (and metadata) are uploaded to AppStore Connect.
This workflow is triggered by every push on master
branch.
Called lanes
fastlane take_screenshots
fastlane upload_metadata
Conclusion
Our CI evolves every day. We try to improve it with each production bug or when we realize the recurrent or forbidding nature of a task.
The build times and the duplicate jobs are valid reasons to reevaluate our processes and git workflow and optimize our CI. Finally, the CI allows to:
- prevent regressions
- keep a good code quality
- avoid human errors
- save time on recurring tasks
- save time during releases process
Moreover, all of these CI flows can only work when respecting our git workflow.
Thanks for reading! In the next article of this series, we will chat about UI tests.
Nicolas Lourenço, iOS Engineer at LumApps