Managing iOS Build Configurations and Schemes for XCTest
Before releasing an iOS app to Apple App Store, it’s very common to have a build that has been targeted to internal audience i.e QA engineers, Product Owners that will run only on specific provisioned devices. During that process, you might have heard the words like DEBUG or RELEASE configuration. When we create the new iOS project in Xcode, Apple gives us two project level build configurations i.e debug and release. The debug configuration is usually used for the development and internal project setting like pointing the app to test environment and release is used to submit an app to App Store. However, there are many situations that might trigger the need for more internal settings or another build configuration(s). As a good iOS developer, you must be writing unit and UI tests using XCTest framework. Usually, developers use debug build configuration or running unit and UI tests but creating a separate build configuration for XCTest makes tests more independent, reliable and deterministic. In this post, we will see how to create the separate build configuration for XCTest.
Before jumping into the creating new build configuration, let’s see what is build configuration in iOS and why it’s important to build an app with the right configuration. Imagine, we have created brand new iOS app in Xcode. It will have two build configurations debug and release spread all over the Build Settings of an iOS project.
While compiling and building an iOS app, there are so many things happens under the hood e.g compiling, linking, copy bundle resource, run build scripts etc. We can always see these things in the xcodebuild logs. The build configuration defines how we want the app to be built under certain settings, e.g Debug Information Format build setting doesn’t need to generate dSYM files when we develop applications using simulators so we can use DWARF for debug builds. However, we need that information for the release builds so that we can use DWARF with dSYM files setting. You can see this all over the build settings in Xcode as shown in the image above. In short, build configuration is defines the mechanism how the app should be build using different conditions or settings. While running our unit or UI tests written using XCTest framework can still use debug configuration, however, adding another configuration gives us lot of control over our testing process.
Build Configuration for XCTest
Now that, we will add another build configuration that can be used just for XCTest either unit or UITests. Generally, unit tests need less configuration as we can have access to the data and APIs directly inside the app. However, UITests or XCUITests are the completely black box so we need more configurations there in order to pass the specific setups for UI tests. Let’s start with adding another build configuration for the sample iOS app from Xcode. In Xcode project level setting, select the Info tab and we will see the list of available configuration and option to add more configurations
Note that, we have to project level configuration to get this option not the target level configuration. Once Clicked on the + button in the configuration. We can able to add another configuration by duplicating either debug or release configuration. Generally, we need to duplicate debug configuration for testing purpose so let’s call it xctest Under the Configurations section, press the + button. Select Duplicate Debug Configuration. Name your new configuration xctest
Now that, we have brand new build configuration that we can use for either unit testing or UI testing. We can assign user-defined settings to the new configuration so that we can set test specific configuration. In the project build settings, scroll down to the very bottom of the build settings, there is a section called User-Defined. This is where your configuration variables will be added and set.
Now that, we have added new build configuration, its time to change the pre-processor macro so that we can set our source code conditionally execute some code
Now we can wake up this configuration using the code in the main app that invokes test code.
// Test only code version code
// App only code
Similarly, we can use the same approach to change the different environments e.g stage, test but its covered in some articles already. Some of the popular articles are
- Multiple Build Configurations in Xcode project here
- Organising xccofig files here
- Managing different environments for iOS projects here
We can use those techniques to configure an app to point it to the test environments. We can also pass these pre-processor macros to the xcodebuild as mentioned here
Using with CocoaPods
If you are already using CocoaPods then, there needs to do some CocoaPods specific changes. As mentioned earlier, Xcode has duplicated the build configuration from the debug configuration. However, CocoaPods has created xcconfig files just for debug and release configuration. We have to explicitly tells CocoaPods to create config files for the another build configurations in the Podfile using the current xcode project.
xcodeproj `your_project.xcodeproj`, 'xctest' => :debug
The snippet above tells CocoaPods that, we have duplicated xctest from the debug build configuration. We also need to add COCOAPODS=1 flag to the pre-processor macro.
Schemes for XCTest
Now that, we have separate build configuration dedicated for executing our unit and UI tests written with XCTest framework. It’s time to prepare environments to run this configuration. Xcode has the mechanism of creating schemes to select the configuration that we want to run. We can create new schemes to run specific configuration in Xcode. Let’s create the new scheme which builds, run and test using xctest configuration from Xcode. From Xcode, we crate new scheme on top of the specific target, You can read more about working with schemes and test targets here
We can edit the newly added scheme BuildConfig-xctest to use xctest build configuration using the Edit Scheme button.
We can see that from left side bar that, we have modified all the actions to use the xctest build configuration apart from profile and release. With xcodebuild if we use this scheme for building and testing then it will use xctest build configuration.
Using the separate build configuration for test purpose, we can make our app more testable as well as reduce the risk of leaking the test code to production. There are a lot of things that can be configured in the app to cover the app with unit, integration and UI tests. Are you using different build configurations for the unit or UI testing? What are your experiences, waive in the comments below