Run your tests for every commit and pull request
Part 6 in a series of tutorials on modern app infrastructure:
Continuous integration (CI for short) is the process of merging your code with a central repository on a regular basis, which might sound good but has pitfalls. For example, if you have multiple team members committing to your GitHub repository several times a day, what happens if someone breaks the master branch? Or what happens if someone writes code that causes a test to fail?
In this final installment of our mini-series on modern app infrastructure, I want to introduce you to automated testing using CircleCI. This is a company that hosts its own macOS servers running various Xcode builds, and can connect directly to your GitHub repository so that every time you make changes they will automatically run your full test suite to double check that all your changes are good.
Even better, as you’ll see you can even have CircleCI run its tests on pull requests to your repository, so that no one – not even you, if you want – can get code into the repository unless your tests pass. The result is that your team can commit code more often and with more confidence.
Now, I know what you’re thinking: CircleCI is a business and they need to make money, so how much does this service cost. Well, it turns out that if your code is open source they’ll waive hosting costs for their beginner plan – you can get all the benefits of CircleCI’s automation without paying a penny.
So, it helps you write safer code, and it doesn’t code anything for open source projects. Best of all, though, is the fact that it takes only a few minutes to set up – seriously, you should give it a try if only to see what real project automation can look like.
The first thing to do is head to https://circleci.com and create an account if you don’t have one already.
Note: If you create your account using your GitHub account, it will automatically configure both GitHub and CircleCI so they can communicate. This drastically simplifies the process, so I suggest you create it this way.
When you’ve logged into CircleCI using GitHub, you’ll see a message like this: “No projects building on CircleCI. Skip to set up new projects” – look for the Add Projects button on the right, and click that.
If you’ve connected CircleCI to GitHub correctly, you should see a list of all your GitHub projects. Look for Paraphrase-Improved in the list of projects (or whatever you called your repository), then click Set Up Project.
Now, even though CircleCI has a free tier for open source projects, you need to email their team to ask them to enable this for you. Seriously, just write an email to firstname.lastname@example.org giving them your username on GitHub and your repository URL, and they’ll set you up with free access. Alternatively, try tweeting @circleci with the same information.
In the meantime, I suggest you select one of their paid-for plans – choose the Growth plan for macOS, because it comes with a two-week free trial. Don’t worry: you won’t be asked for credit card information, so there’s no chance of you being charged by accident. Selecting the free trial lets you continue while the CircleCI team validate your email request.
Once you have an active plan, the CircleCI site will give you a set of instructions to follow. Here’s the simplified form:
touch .circleci/config.ymlto create a configuration file in there.
open .circleci/config.ymlto open config.yml in a text editor – this is the configuration file that CircleCI reads when working with your project.
git add .from your terminal, to add that configuration file to Git’s staging area.
git commit -m “Adding CircleCI”then
git pushto commit that change and push it up to GitHub.
At this point CircleCI takes over: it will check out your source code, fetch the CocoaPods you need, build the whole thing, then run your tests and report back how things went. Even better, if there are any failures you’ll automatically get an email showing you what went wrong.
Because CircleCI likes to work with a clean environment for every test, that means it needs to fetch the CocoaPods master spec repository every time a test is run. As you saw in part two this can take a long time, so it’s likely to take about 8 minutes for CircleCI to finish testing our app.
Because CircleCI is now watching our project, every new commit that happens in the future will automatically trigger a new build. If you want to test this for yourself, wait until the previous test finished then try pushing up a minor change – maybe just add a line to README.md, for example. As soon as it’s pushed, you’ll see your CircleCI dashboard update to say another test is running. Perfect!
At this point, CircleCI has run an initial test on our app to make sure everything is working, and if you pushed up another small change then that will be have been tested too – CircleCI is automatically checking that all changes don’t break our tests.
Although this is an improvement, it’s not ideal: we’re doing reactive checking of changes, rather than proactive – we’re waiting for things to break, and it’s preferable to avoid that entirely where possible.
To fix this we’re going to lock down the master branch of our repository so that no one can commit directly to it. Instead, we’re going to force everyone, including ourselves, to commit their code to a branch that CircleCI can then check before it gets merged into master.
This is so incredibly easy to do that I think you’re going to want to switch it on immediately and keep it around – it’s the kind of simple change that will really help take your software development process to the next level.
Here’s how to set it up:
Click Save Changes, and enter your password to confirm your new settings.
We’ve just told GitHub that no one – including ourselves – should be allowed to push changes directly to the master branch of our project.
Try it now – try changing README.md again, adding it to Git’s staging area, then using
git push to send it up to GitHub. You should get errors back saying “remote: error: GH006: Protected branch update failed for refs/heads/master” and “remote: error: Required status check "ci/circleci" is expected” – that’s GitHub enforcing the rules we just set.
Instead we need to push our change to a remote branch, using a slightly different command:
git push origin master:new_readme
That pushes our local master branch up to the remote “new_readme” branch – creating it as necessary.
Circle will detect the new branch and immediately start checking our change. If you refresh your GitHub repository page you’ll see “Your recently pushed branches” followed by the “new_readme” branch you just pushed.
At the right side of that message you’ll see “Compare & pull request”, which starts the process of merging that branch with master. You should go ahead and add information here telling your team what changed and why, then click Create Pull Request.
Now, normally you’d see a green button saying “Merge Pull Request” that handles the process of merging the branch you created with the master branch, but it’s now going to be disabled – GitHub is refusing to let you merge the pull request because CircleCI’s tests haven’t finished yet!
This is exactly what we want: no one can break the master branch of our repository any more because CircleCI is actively guarding against it. If their build fails, or if any tests fail, CircleCI will refuse to let us merge.
In this case, our change was trivial, so after a few minutes CircleCI will report a green light back to GitHub, and the Merge Pull Request button will automatically become active. Go ahead and click that, then Confirm Merge, to have the whole process take place for us. When it completes, a new Delete Branch button will appear because our temporary branch is no longer needed – click that one too.
At this point CircleCI is automatically running our tests and letting us know if any fail, and it’s also automatically verifying pull requests so no one can break our build by accident – this is a huge step forward for our little project.
However, before we’re done I’d like you to make one small change. If you followed my advice when we were setting up CocoaPods, you checked your Pods directory into source code. Right now CircleCI takes seven or eight minutes to complete its tests because it has to download and install the master spec repository from CocoaPods, but because our project already has its Pods directory in the Git repository we don’t actually need that to happen.
So, open your config.yml file and look for this commands:
# Install CocoaPods - run: name: Install CocoaPods command: pod install
Those are the ones that trigger the massive CocoaPods download, so go ahead and add a
# symbol before each of them – that will comment them out so that they don’t happen. Once you commit that change to GitHub, your CircleCI test times will go down from about 8 minutes to about 1 minute – a huge improvement!
This was the last installment of a mini-series on upgrading your app for modern iOS infrastructure. In this article you learned how a continuous integration service like CircleCI can help you ensure your tests are being run regularly, and also protect your code from accidental problems – this draws up on the testing code we did in part one, the Fastlane work we did in part four, and the GitHub work we did in part five, all at once.
This app is still a long way from perfect, but it’s significantly better now than it was at the start of the series. Hopefully you’ll be able to apply these same techniques to your projects and enjoy the same benefits – good luck!
LEARN SWIFTUI FOR FREE I have a massive, free SwiftUI video collection on YouTube teaching you how to build complete apps with SwiftUI – check it out!
Paul Hudson is the creator of Hacking with Swift, the most comprehensive series of Swift books in the world. He's also the editor of Swift Developer News, the maintainer of the Swift Knowledge Base, and a speaker at Swift events around the world. If you're curious you can learn more here.