For many companies, testing and releasing are still very blurry processes, which don’t seem to work as they should. Testing is mostly manual, slow and error-prone. Releasing is usually also manual and slow, making its frequency hard to maintain on a sprint-basis. Continuous Integration (CI) and Continuous Deployment (CD) are all about automating both testing and releasing, making it possible for a team to release every week or two. At EGYM, we have achieved just that and I am going to tell you all about it!

Testing & Automating is easy, but doing it at scale isn’t.

🎬 Hi there, I’m Jean!

In this article, I want to share with you my experience in our Path to CI / CD Nirvana in iOS, at EGYM! 😃

Continuous Integration (CI)


CI is the process of ensuring that a new change added on top of previous ones won’t break what has been built so far, making developments smooth and flawless. 🏎

That pretty much means most of the time “Testing before Merging”. ✅

Do you remember the testing pyramid? Well, I won’t go through it in detail, but we have pretty much 3 layers, Unit Testing, Integration Testing and finally UI Testing. All of those are crucial for ensuring the quality of your app, but the one, in particular, I want to focus with you today is UI Testing. 📱

We all know it, UI Testing is a pain when it comes to stability and maintainability, but don’t you worry, I might just have what you are looking for! 🧐

UI Testing in iOS - The Ultimate Guide

That’s right, I made a complete guide for you to get started with stable, maintainable and scalable UI testing in iOS! 🎉

🔌 Mocking the Network

The first thing you need to take care of is Mocking the Network. You don’t want to let your network requests hit an actual network and there are mainly two reasons for that.

Isolation. Of course, what if your backend friends break their test server? Well, your UI Tests will fail as well, even though nothing is wrong with the app 😱 So now you’ll say “Well let’s deploy a dedicated environment for it then!”. And whose ownership will that be? iOS devs? Or Backend devs? Isn’t that a little bit over-engineering? 🤔 Mocking the Network allows you to avoid unnecessary headaches and “Just re-trigger it!” kind of workflows. 😆

Monitoring. Well yes, if you mock or stub the network, that means you need to predict exactly how many calls are going to be made during your UI test and that’s extremely valuable because you might sometimes find out that your app is actually making unnecessary calls which are eating your user’s data! 😮

Overall, Mocking the Network gives you full control and helps to keep your UI tests stability to a very high standard! 💪

If you want to read more about Mocking the Network, go ahead and check out my dedicated article! 👀

🚦 Stubbing the Navigation

The second thing you want to address is Stubbing the Navigation. One major pain point with iOS UI Testing is that every test will always relaunch the app entirely. This means that you are forced to go through several screens manually before getting to the one you actually want to test. That breaks the isolation principle, making UI tests hard to debug. 😕

One way of solving this is to inject a stub that will route your navigation in the exact screen you want to end up on. Of course, that also means that your navigation logic must be centralised in a dedicated layer like the Coordinator for instance! 🚦

That way, your UI Tests will suddenly become blazingly fast, but also more isolated, stable and easier to maintain! 🚀

If you want to read more about Stubbing the Navigation, go ahead and check out my dedicated article! 👀

📺 Disabling Animations

The third action you might want to take is Disabling Animations. This one is a quick and easy win, simply disabling animations while UI testing will make your tests not only faster but more stable as animations won’t be a potential risk for your assertions. 👍

If you want to read more about Disabling Animations, go ahead and check out my dedicated article! 👀

🔮 Generating Accessibility Identifiers using Reflection

Until now, Accessibility Identifiers have always been assigned manually. This is a pain and most importantly unnecessary noise in your code so you should avoid doing this. Generating Accessibility Identifiers using Reflection is a great way to get rid of all this manual work, but instead, let your code generate it for you at runtime! 🤯

If you want to read more about Generating Accessibility Identifiers using Reflection, go ahead and check out my dedicated article! 👀

🤖 Implementing the Robot Pattern

Finally, UI Tests are not only meant to be verifying that your app still behaves as expected, but they also can serve as implicit documentation of how your app works, what the main features are, for your new hires who do not have a clue about the product yet! 😮

Implementing the Robot Pattern will not only help you separate the What from the How of your UI Tests and make screen assertions reusable across UI Tests, but also provide a more human-readable way of specifying tests! 😎

If you want to read more about Implementing the Robot Pattern, go ahead and check out my dedicated article! 👀

Continuous Deployment (CD)


CD is the process of deploying a new version of the product, for every new change that is made, making deployments incremental meaning, less risky than releases sent-out in big chunks. This process should ideally be fully automated, just like CI. 🚀

Fastlane Swift

When it comes to automation for iOS we, at EGYM, use Fastlane Swift.
It is Fastlane but in Swift! 😱🍾

The choice of Fastlane was simple, we must be able to trigger automated tasks remotely as well as locally. And using Fastlane Swift, you can democratise your CI / CD automation to the whole iOS team, as they don’t need to learn Ruby anymore: their extensive Swift knowledge will do! 👌

Automate Everything

Currently, we have a total of 12 lanes:

✅ Run Tests (Unit / Integration / UI)

Obviously, running tests is the first process you might want to automate. 🤖
In Fastlane Swift, this is easy, simply call the runTests action from Fastfile.swift and you’re all set! 🚀

At EGYM, we run the tests on every Pull Request.

📸 Capture Screenshots

Now, the capture screenshots lane is pretty similar to the run tests one, simply call the action captureScreenshots from Fastfile.swift and if you want to be fancy, also call the frameScreenshots action to add the device’s frame around your screenshots! 🖼

At EGYM, we use this lane to capture, frame and share our localised screenshots with our designers so they can reuse them for marketing purposes, this saves them a lot of time and they’re really grateful for it! 🤗

✈️ Upload To Testflight

Of course, uploading to Testflight is also crucial to your development team. 👨‍✈
It requires a little more logic though, so hold on to your seats! 💺

Here are the actions you need to sequentially use:

  • Get the latest Testflight build number using latestTestflightBuildNumber;
  • Increment the build number using incrementBuildNumber;
  • Build the app using buildApp;
  • Upload a new build to Testflight using uploadToTestflight;
  • Download .dSYMs using downloadDsyms;
  • Upload .dSYMs to Crashlytics using uploadSymbolsToCrashlytics;

At EGYM, we automatically submit a new Testflight build after every Git merge to our develop branch.

📱 Upload To AppStore

Now, uploading to AppStore is a whole different story. 😆

Overall, here are the steps we follow at EGYM:

  • Generate Metadata from our AppStore.strings localization file ;
  • Download Screenshots from a Google Cloud Storage bucket ;
  • Upload a new build to Testflight ;
  • Upload to AppStore the metadata, screenshots and link the Testflight build ;
  • Increment the version number (patch, minor or major) ;
  • Add, commit and push the changes (build increment) ;
  • Create Pull Requests ;
  • Track Deployment Frequency.

At EGYM, our AppStore releases are triggered manually from Bitrise, but once triggered, everything else is automated, including sending for Apple Review and releasing using Phased Rollout! 🚀

🐛 Upload To Crashlytics Beta

Unfortunately, AppStoreConnect’s build processing can take quite some time (~20 minutes); therefore, we also like to use Crashlytics Beta to distribute our development builds. 👍

At EGYM, we automatically submit a new Crashlyitics Beta build on every Git push.

📝 Sync Code Signing

Syncing Code Signing Certificates and Profiles is a very trivial process using Fastlane, simply call the action syncCodeSigning from Fastfile.swift and you are good to go! 🚗

At EGYM, we mostly use this lane for downloading development certificates.

🛬 Download Translations

Remembering the exact CLI parameters can be a pain sometimes, that is why we also have a lane for managing our Lokalise translations! 🔡

At EGYM, our Download Translations lane simply calls the lokalise CLI under the hood.

🛫 Upload Translations

Similarly to Downloading Translations to Lokalise, we also have a lane for uploading them. Same way as the downloading lane, the upload one also uses the lokalise CLI under the hood. 🔤

🖨 Generate Metadata

Fastlane Metadata is a folder/file representation of what information AppStore Connect expects from your app when releasing. 🚀

At EGYM, we’ve automated the generation of Metadata by creating a Swift script expecting a localisation file (AppStore.strings) as input! 🤖

✨ Update Dependencies

Updating dependencies automatically is very helpful, its main purpose is to let you know when a dependency has a new version, by creating a Pull Request when something has changed in any of your .lock or .resolved files. 🔐

Simply create a lane that executes for instance bundle update or carthage update and submits a Pull Request! 📝

At EGYM, we use it to update our Gemfile.lock and Cartfile.resolved files! 👌

🖼 Update Screenshots

As capturing screenshots can take quite some time, doing it as part of the release process might not be very wise. 🤔

Instead, it might be more efficient to upload the screenshots inside a Google Cloud Storage bucket and update them on a weekly / monthly basis! 💡

At EGYM, we currently host about 220 screenshots inside a dedicated Google Cloud Storage bucket, in 2 different languages for 3 different devices! 🤯

🎫 Move Ticket to Ready for Acceptance Testing

As I said earlier, we automate everything. And that includes moving tickets in our Sprint Dashboard! 😆

Whenever we send a build to Testflight after a merge, once the build processing has been completed, we move the corresponding ticket to “Ready for Acceptance Testing” and send a message on Slack to our QA testers! 🚀

I know, that’s babysitting at this point but well, our QA testers love it! 😍

Fully Automated Release - The Holy Grail

That’s right, we made it, the holy grail: a fully automated release! 👑

This was a long journey, but by combining the power of both Bitrise and Fastlane, we’ve managed to submit our app for AppStore release, create version bump Pull Requests and track our Deployment Frequency, with a simple click of a button! 😱

Here is the full sequence of actions we perform automatically:

  1. Create branch “release-(version)” (from develop)
  2. Download AppStore.strings localisation file from Lokalise
  3. Generate metadata from AppStore.strings localisation file
  4. Download screenshots from Google Cloud Storage bucket
  5. Upload new build to Testflight
  6. Upload DSYM to Crashlytics
  7. Release to AppStore with metadata, screenshots and Testflight build
  8. Add Git tag “releases/(version)”
  9. Increment Version Number (patch, minor, major)
  10. Commit and Push
  11. Create Pull Requests (develop, master)
  12. Track Deployment Frequency in Deployment Logbook

All we need to do is trigger our UploadToAppStore workflow on Bitrise from the branch develop and a few minutes later, we get a Slack message confirming that the release was successful, along with 2 freshly created Pull Requests, one for the actual release (master) and one for the Version Bump (develop)! 🚀

Conclusion

It’s been a long journey, but definitely worthwhile! 🎉
Our workflows work so well, that we are able to catch bugs early on in our development process, as well as keeping a high Deployment Frequency without compromising on our developments speed! ✈️

Worth a try? Would you like to follow our path to CI / CD Nirvana? If so, follow me on Twitter, I’ll be happy to answer any of your questions and you’ll be the first one to know when a new article comes out! 👌

Until next time, happy automating! 🤖

Bye Bye! 👋