Important Note
With Xcode 5.1 (possibly an earlier Xcode), test is a valid build action.
We were able to replace the entire hack below with an xcodebuild call using the test build action and with the appropriate -destination parameters. man xcodebuild for more information.
Information below is for posterity
I tried to hack Apple scripts to run unit tests as indicated in
Run Xcode 4 unit tests from the command line
and
Xcode4: running application tests from the command line in iOS
and numerous similar publications on the Internet.
However, I had a problem with these solutions. Some of our unit tests used iOS Keychain, and these calls when working in an environment caused by hacking Apple scripts failed with an error ( errSecNotAvailable [- 25291] for strangely curious people). As a result, tests always failed ... an unwanted function in the test.
I tried a number of solutions based on the information I found elsewhere on the Internet. Some of these solutions included, for example, trying to launch the iOS simulator security services daemon. After I struggled with them, the best option seemed to be running in an iOS simulator with the full advantage of a simulator environment.
What I did then was get the iOS Simulator ios-sim launch tool. This command line tool uses Apple's private frameworks to launch an iOS application from the command line. However, it was especially useful to me that it allows me to pass both environment variables and command line arguments to the application that it runs.
Despite the environment variables, I managed to get the Unit Testing package introduced in my application. Through command line arguments, I can pass "-SenTest All" to get the application to run unit tests and exit.
I created a circuit (which I called "CommandLineUnitTests") for my module testing module and checked the "Run" action in the build section, as described in the messages above.
Instead of hacking Apple scripts, I replaced the script with the one that launches the application using ios-sim, and sets up the environment for entering my module testing module into the application separately.
My script is written in Ruby, which is more familiar to me than BASH scripting. Here's the script:
if ENV['SL_RUN_UNIT_TESTS'] then launcher_path = File.join(ENV['SRCROOT'], "Scripts", "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("#{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
Running this from the command line looks like this:
xcodebuild -sdk iphonesimulator -workspace iPhoneApp.xcworkspace/ -scheme "CommandLineUnitTests" clean build SL_RUN_UNIT_TESTS=YES
After searching for the environment variable SL_RUN_UNIT_TESTS , the script finds the "launcher" (iOS-sim executable file) in the project source tree. He then constructs the path to my unit batch testing based on the build parameters that Xcode passes in the environment variables.
Then I create a set of runtime variables for my running application that inject the unit testing package. I set these variables in the environment hash in the middle of the script, and then used some ruby ββgrunge to combine them into a series of command line arguments for the ios-sim application.
Next to the bottom, I take TEST_HOST from the environment as the application that I want to run, and the system command actually performs the ios-sim transfer of the application, the arguments of the command to configure the environment and the arguments -SenTest All and the path of the test package to the running application.
The advantage of this scheme is that it runs unit tests in a simulator environment, since I believe that Xcode itself. The drawback of the circuit is that it uses an external tool to run the application. This external tool uses Apple's private frameworks, so it can be fragile with subsequent releases of the OS, but it works at the moment.
PS I used βIβ a lot in this post for storytelling reasons, but the great merit of my partner in crime is Pavel, who worked with me with these problems.