Automating iOS Unit Tests

3Pillar’s expertise in designing, developing, and deploying mobile apps for the iPhone and iPad has given us the opportunity to develop a number of internal best practices for testing mobile apps. Testing is a vitally important phase of the development life cycle, as it accelerates time-to-market and increases a team’s velocity when done well. To deploy native iOS applications for any large or small enterprise client, running automated unit tests is very useful. This helps mobile developers to create an app which is easy to use, maintainable and extensible.

This blog post provides a method to enable iOS unit tests to be run from the command line. Mobile app developers now have the ability to run unit tests before a build is produced on Hudson. The solution was developed for libraries as well as apps, so you can be more confident when making changes to shared code (provided they have unit tests of course). In addition to running the tests, the reporting mechanism is built into Hudson, as we have employed a Ruby script that converts OCUnit output into a JUnit friendly XML document.

This method is a combination of solutions from Raingrove, RedGreenFactor, and StackOverflow.

Setting up the build environment

This section is for setup on a build machine. If you are only setting up your project for unit tests, please refer to the next section.

1. Download and install ios-sim.

In short, ios-sim is a command line application that allows you to launch an app via the iOS simulator without opening Xcode.
You can then pass command line and environment variables to the app.

  • From the directory containing a Rakefile, run this to build the ios-sim application: rake install prefix=/usr/local/
  • The executable “ios-sim” will then be created in this directory: YOUR_PATH/ios-sim/build/Release

2. Download and install OCUnit2JUnit.

  • The script can be downloaded here: OCUnit2JUnit.
  • After unzipping, the ruby script will be found here: YOUR_PATH/OCUnit2JUnit/bin/ocunit2junit.rb

3. Add ios-sim and ocunit2junit.rb to your path

  • Modify your bash profile so these tools will always be available: vi ~/.bash_profile
  • Add these lines to the file:
PATH=$PATH\:/YOUR_PATH/ios-sim/build/Release
PATH=$PATH\:/YOUR_PATH/OCUnit2JUnit/bin
  • After saving, exit your terminal session and restart tomcat.

Setting up Xcode projects to run unit tests from the command line
App or Library Projects

1. Create a new scheme for the unit test target naming it “UnitTests” (convention).

2. Edit the scheme and check the test target’s Run box under Build.

3. Go to Manage Schemes… and check “Share”.

4. Select the test target and then the Build Phases tab.

5. Open the Run Script and replace “${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests” with  “${PROJECT_DIR}/RunUnitTests.rb”

6. Add “i386” to Valid Architectures to this and any targets that UnitTests depend on.

7. Set “Build Active Architecture” to “No” for the library.

8. Place the RunUnitTests Ruby script below into the .xcodeproj’s directory:

#!/usr/bin/env ruby
system("echo Running Build Script")
if ENV['SL_RUN_UNIT_TESTS'] then
    launcher_path = "ios-sim"
    test_bundle_path = File.join(ENV['BUILT_PRODUCTS_DIR'], "#{ENV['PRODUCT_NAME']}.#{ENV['WRAPPER_EXTENSION']}")
    environment = {
        'DYLD_INSERT_LIBRARIES' => "/../../Library/PrivateFrameworks/IDEBundleInjection.framework/IDEBundleInjection",
        'XCInjectBundle' => test_bundle_path,
        'XCInjectBundleInto' => ENV["TEST_HOST"]
    }
    environment_args = environment.collect { |key, value| "--setenv #{key}=\"#{value}\""}.join(" ")
    app_test_host = File.dirname(ENV["TEST_HOST"])
    system("echo #{launcher_path} launch \"#{app_test_host}\" #{environment_args} --args -SenTest All #{test_bundle_path}")
    system("#{launcher_path} launch \"#{app_test_host}\" #{environment_args} --args -SenTest All #{test_bundle_path}")
else
    puts "SL_RUN_UNIT_TESTS not set - Did not run unit tests!"
end

9. Change the file permissions of RunUnitTests.rb (if needed): chmod 755 RunUnitTests.rb

10. Use this command to build and run the tests (apps only):

xcodebuild -sdk iphonesimulator -scheme UnitTests clean build SL_RUN_UNIT_TESTS=YES 2>&1 | ocunit2junit.rb

Library Projects Continued

The above setup only works because an app exists for the unit tests to be injected into. When using the same configuration shown above on a library, you will get a build error because xcodebuild won’t find an app.

To run unit tests for a library project, follow the above steps for an app/library, as well as making these additional configurations:

1. Create a new target as an empty application with no unit tests, naming it “UnitTestsApp”.

  • Select the app target → Build Phases.
    • ​Add the library as a target dependency.
    • Link the app target’s binary with the library (the .a file).
  • Select the unit test target → Build Phases.
    • Remove the library target dependency.
    • Add the app as a target dependency.
    • Go to Build Settings.
    • Set Bundle Loader to:
    • $(BUILT_PRODUCTS_DIR)/UnitTestsApp.app/UnitTestsApp  
    • Set Test Host to:
    • ${BUNDLE_LOADER}

2. Any target that links its binary to the library’s .a file must be configured with special linker flags and header search path.

In this example, the app target was linked manually, so its flags/paths definitely need to be configured.
While the unit test target came automatically linked, its flags and paths still need to be manually configured:

Go to Build Settings.

  • Set Other Linker Flags to:
  • -ObjC -all_load
  • Set Header Search Paths to:
  • $(SRCROOT)/Libraries/**
  • Unselect the Header Search Paths field and then double-click it again to set it to ‘recursive’.
  • Repeat a-d for each target linking its binary to the library

3. The library’s unit tests can now be run just like an app’s unit tests:

xcodebuild -sdk iphonesimulator -scheme UnitTests clean build SL_RUN_UNIT_TESTS=YES 2>&1 | ocunit2junit.rb

Hudson Job Configurations

  1. Check the Publish JUnit test result report box under Post-build Actions.
  2. After the Test reports XML field appears, enter this:

    **/test-reports/*.xml
Greg Kraft

Greg Kraft

QA Engineer and Mobile Developer

Greg Kraft is a QA Engineer and mobile developer at 3Pillar Global. Greg’s responsibilities include QA testing and iOS development, as well as managing and deploying builds for rapid development of iOS apps, Android apps, and other custom software products. Prior to 3Pillar, he worked as a Mobile Web QA Lead at Aegis Mobile.

Leave a Reply

Related Posts

How to Manage the “Need for Speed” Without Sacri... The pace of innovation today is faster than it has ever been. Customers are much more active and vocal thanks to social and mobile channels, and the c...
Determining the First Release The first thing you release needs to put the solution to your customer's most important problem in their hands. Deciding what the most important probl...
The Art of Building Rapid (and Valuable) Proofs of Concept Clients and stakeholders want results. They want assurances that their investment is well spent and they're building the right product. The software d...
Are You Doing Stuff or Creating Value? You can put a bunch of stickies on the wall, create tons of JIRA tickets, and commit lots of code, but are you creating value? Is the work your produc...
Costovation – Giving Your Customers Exactly What They ... On this episode of The Innovation Engine podcast, we delve into “cost-ovation,” or innovation that gives your customers exactly what they want – and n...