Continuous Integration (CI) is a powerful practice to keep any app in a stable state throughout its development. Here's how you set up CI for iOS apps to automatically perform code validation and testing whenever your code changes.
iOS Continuous Integration Using Travis CI and Fastlane
Written by
This post will cover an easy-to-setup approach to CI for iOS apps. The setup uses Travis CI as CI provider, the reason being that Travis CI is cloud-based and has a free-plan for open-source projects - even for building on macOS (usually not the case with other CI providers). So let's not waste time and take our apps to the next level!
Setting up Travis
So I'm just going to quote the initial setup from the Travis CI docs here:
- Go to Travis-ci.com and Sign up with GitHub.
- Accept the Authorization of Travis CI. You’ll be redirected to GitHub.
- Click the green Activate button, and select the repositories you want to use with Travis CI.
Bundler + Gemfile dependencies
We'll be using Bundler so we have a Gemfile specifying all the RubyGem dependencies that we need. That will make it easier when we need to install the dependencies on Travis CI.
If you don't have Bundler installed already on your Mac, do it by running gem install bundler
. If you don't have permissions, you need to have rbenv installed or sudo
it if you're brave.
Navigate to the directory of your project and run bundle init
. That will create a Gemfile for you. Here are the dependencies we need to install, so copy/paste this code into your newly created Gemfile:
source "https://rubygems.org" gem "danger" gem "danger-swiftlint" gem "fastlane" gem "xcode-install"
Now that we've specified the dependencies we need, install them by running bundle install
. This also adds a Gemfile.lock to your project, specifying the versions of the dependencies that were installed.
Travis CI environment variables
We need to provide Travis CI with two access tokens to do its thing. Go to GitHub's Personal Access Token in Settings and generate a new access token. You can name it "travis" if you like. Then you need to go to the environment variables section of your repo in Travis CI. It should be under Settings. You then need to create these two environment variables and copy/paste the access token into them:
- DANGER_GITHUB_API_TOKEN
- GITHUB_API_TOKEN
That should give Travis CI the access it needs.
Creating .travis.yml with stages/jobs
Now, onto the more interesting stuff. Create a file in the root of your project and name it ".travis.yml". This is where the CI magic happens - here, we will define all the jobs that we want to run. Naturally, depending on your project, this file will differ greatly. So I'll just share how it looks in my app QCards and take it from there:
# environment setup language: swift osx_image: xcode11.1 # caching of dependencies cache: bundler: true directories: - Carthage # run jobs on changes to master only branches: only: - master # prepare dependency managers before_install: - brew update - brew outdated carthage || brew upgrade carthage - gem install bundler -v "$(grep -A 1 "BUNDLED WITH" Gemfile.lock | tail -n 1)" # install dependencies install: - travis_wait carthage bootstrap --platform iOS --cache-builds - bundle install # jobs and stages to run jobs: include: - stage: "Code validation" name: "Code validation" script: - bundle exec danger - stage: "Test" name: "Unit tests" script: - bundle exec fastlane unit_test - stage: "Test" name: "UI tests" script: - bundle exec fastlane ui_test
What you need to take away from this is that we have two stages: "Code validation" and "Test".
- "Code validation" is our first stage which contains a single job that triggers Danger. If all of the jobs in a stage passes, we proceed to the next stage. In this case, we only have one job that needs to pass to proceed.
- Our second stage "Test" contains two jobs that will run in parallel: "Unit tests" and "UI tests". The command
bundle exec fastlane [lane_name]
simply triggers a lane in our Fastfile which, in turn, triggers the execution of the tests.
if you're not familiar with the concepts of jobs and stages, I recommend that you read about the job lifecycle and build stages from the Travis CI docs.
Code validation using Danger + SwiftLint
Danger is one of the dependencies we specified in our Gemfile earlier. It's a great tool to improve our code reviews and simplify pull requests to our project. The Danger docs describe itself like this:
Danger runs during your CI process, and gives teams the chance to automate common code review chores.
These chores could be anything from formatting issues in the code to formalities such as not leaving a description in the pull request. You decide on the rules using the Dangerfile. What happens is that Danger will leave messages in your pull requests when you violate any of the rules. Here's the rules that I use in this Dangerfile and you can expand it with your own rules.
We also specified "danger-swiftlint" in the Gemfile. It's a plugin for Danger that allows us to use SwiftLint in combination with Danger to perform linting and report back if there are any violations. Create a ".swiftlint.yml" file in the root of your project. You can copy/paste the linting rules that I normally use here. Lastly, make sure that you have these two lines in your Dangerfile:
swiftlint.config_file = '.swiftlint.yml' swiftlint.lint_files inline_mode: true
That's the code validation! Now, onto the automated testing.
Testing using Fastlane scan
We are using Fastlane scan to run our tests. So we need to set up Fastlane by running fastlane init
at the root of our project and select: "4. Manual setup - manually set up your project to automate your tasks". That will create a Fastfile inside the newly created fastlane folder.
Now, go ahead and run fastlane scan init
in your project to create a Scanfile inside the fastlane folder. This is a configuration file with all the default parameters we want to set when we run the tests. It could look like this, but you want to check the docs to see what makes sense in your project:
devices(["iPhone 11 Pro"]) reset_simulator(true) clean(true)
In your Fastfile, create one or more lanes where you'll run your tests. In my case, it looks like this:
setup_travis default_platform(:ios) before_all do xcversion(version: "11.1") end platform :ios do desc 'Runs the unit tests in QCardsTests' lane :unit_test do scan(scheme: "QCardsTests") end desc 'Runs the UI tests in QCardsUITests' lane :ui_test do scan(scheme: "QCardsUITests") end end
It goes without saying that you should change the code to match the schemes in your project.
Now you should be all set with the testing. If you've come this far, you should be able to create a pull request with changes to your code and see the CI in action. If everything is successful, you should see a screen like this in Travis CI:
Conclusion
That's it! A good CI setup can make you and your team members lives a whole lot easier. It may even determine if your app makes or breaks it during development. You can find the source code for this exact setup on GitHub.
In the next posts, we'll expand upon this setup so that we can automate code signing and deployments to App Store.
Thanks for following along!
Share this post
Facebook
Twitter
LinkedIn
Reddit
You may also like
Automation
Automate Screenshot Framing with Text Using Fastlane
Framing your App Store screenshots greatly improves your store listing. But doing it manually takes hours away from perfecting your app. Instead, here’s how you automate the process of adding device frames and text to your screenshots.
Automation
The Ten Commandments of iOS Development
Here’s a set of key principles to guide you to do iOS development according to best practices. They are all about the bigger picture of iOS development, including processes and architecture decisions – not simply language specific challenges.
DevOps
Architecting an Analytics Service for iOS Apps
Monitoring the behavior of your app’s users is critical to the success of your app. If the only feedback you get from your users are App Store review, now is the right time to start using analytics. We’ll go over how you create an analytics service that’s modular and easily extensible using a clean iOS architecture.