Monday, April 29, 2013

Automated Unit Testing with Jenkins, Cocoapods and GHUnit



1. Install homebrew link.
Or paste the command in terminal:
$ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"

2. Install cocoapods http://cocoapods.org/.

$ sudo gem install cocoapods
$ pod setup

3. Add GHUnit to your podfile.

pod 'GHUnitIOS', '~> 0.5.6'

Common Error: When I first uploaded my repo to Jenkins, I got an error about Jenkins not being able to clone a submodule for Cocoapods, and if you're having this issue you have to make sure to delete anything related to Cocoapods in your .gitmodules file.

Common Error: We added KIF to our project as a cocoapods dependency, but your app will be rejected if you include it in the build you submit to the app store. The solution is to only include KIF for your test target that uses KIF and not your App Store release version of your app. This can be done in your cocoapods file.

Example:
For command line builds XCode defaults to release version (which you can change), and my KIF tests were in the target IntegrationTests.
target :release, :exclusive => true do
  link_with 'IntegrationTests'
 pod 'KIF', '~> 0.0.1'
end

KIF.podspec
Pod::Spec.new do |s|
  s.name         = "KIF"
  s.version      = "0.0.1"
  s.summary      = "KIF, which stands for Keep It Functional, 
is an iOS integration test framework."
  s.homepage     = "https://github.com/square/KIF"
  s.license  = 'MIT'
  s.author       = 'efirestone', 'jpsim'
  s.source       = { :git => "https://github.com/square/KIF.git",
 :commit => "ed057a038f07232e351210e56971fd9440acdd42" }
  s.platform     = :ios, '5.0'
  s.source_files = 'Classes','Additions',
  s.frameworks = 'UIKit', 'Foundation'
end

4. Install Jenkins with homebrew:

$ brew install jenkins

5. Add an alias to your .bashrc file and add an alias to start and stop Jenkins.

.bashrc
alias jenkins_start="launchctl load /usr/local/Cellar/jenkins/1.499/homebrew.mxcl.jenkins.plist"
alias jenkins_stop="launchctl unload /usr/local/Cellar/jenkins/1.499/homebrew.mxcl.jenkins.plist"
6. Start Jenkins server by typing the command in terminal:
$ jenkins_start
7. Use this Makefile:
default:
 # Set default make action here
 # xcodebuild -target Tests -configuration MyMainTarget -sdk macosx build 

clean:
 -rm -rf build/*

test:
 GHUNIT_CLI=1 WRITE_JUNIT_XML=1 /usr/bin/xcodebuild -target TARGET_NAME -configuration Debug -sdk iphonesimulator build -project PROJECT_FOLDER/PROJECT_NAME.xcodeproj 

8. In Jenkins, create a job and execute a shell script with the following command:
make test

9. Create an output job to publish the JUnit XML output. By default, the XML results are located in the PROJECT_NAME/build/test-results folder.




10. Save and apply settings.

11. In XCode, add a Run Script build phase and add these scripts in the folder that contains your Xcode project.
sh RunTests.sh

RunTests.sh
#!/bin/sh

# If we aren't running from the command line, then exit
if [ "$GHUNIT_CLI" = "" ] && [ "$GHUNIT_AUTORUN" = "" ]; then
  exit 0
fi

export DYLD_ROOT_PATH="$SDKROOT"
export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR"
export IPHONE_SIMULATOR_ROOT="$SDKROOT"
#export CFFIXED_USER_HOME="$TEMP_FILES_DIR/iPhone Simulator User Dir" # Be compatible with google-toolbox-for-mac

#if [ -d $"CFFIXED_USER_HOME" ]; then
#  rm -rf "$CFFIXED_USER_HOME"
#fi
#mkdir -p "$CFFIXED_USER_HOME"

export NSDebugEnabled=YES
export NSZombieEnabled=YES
export NSDeallocateZombies=NO
export NSHangOnUncaughtException=YES
export NSAutoreleaseFreedObjectCheckEnabled=YES

export DYLD_FRAMEWORK_PATH="$CONFIGURATION_BUILD_DIR"

TEST_TARGET_EXECUTABLE_PATH="$TARGET_BUILD_DIR/$EXECUTABLE_PATH"

if [ ! -e "$TEST_TARGET_EXECUTABLE_PATH" ]; then
  echo ""
  echo "  ------------------------------------------------------------------------"
  echo "  Missing executable path: "
  echo "     $TEST_TARGET_EXECUTABLE_PATH."
  echo "  The product may have failed to build or could have an old xcodebuild in your path (from 3.x instead of 4.x)."
  echo "  ------------------------------------------------------------------------"
  echo ""
  exit 1
fi

# If trapping fails, make sure we kill any running securityd
#launchctl list | grep GHUNIT_RunIPhoneSecurityd && launchctl remove GHUNIT_RunIPhoneSecurityd
#SCRIPTS_PATH=`cd $(dirname $0); pwd`
#launchctl submit -l GHUNIT_RunIPhoneSecurityd -- "$SCRIPTS_PATH"/RunIPhoneSecurityd.sh $IPHONE_SIMULATOR_ROOT #$CFFIXED_USER_HOME
#trap "launchctl remove GHUNIT_RunIPhoneSecurityd" EXIT TERM INT

RUN_CMD="\"$TEST_TARGET_EXECUTABLE_PATH\" -RegisterForSystemEvents"

echo "Running: $RUN_CMD"
set +o errexit # Disable exiting on error so script continues if tests fail
eval $RUN_CMD
RETVAL=$?
set -o errexit

unset DYLD_ROOT_PATH
unset DYLD_FRAMEWORK_PATH
unset IPHONE_SIMULATOR_ROOT

if [ -n "$WRITE_JUNIT_XML" ]; then
  MY_TMPDIR=`/usr/bin/getconf DARWIN_USER_TEMP_DIR`
  RESULTS_DIR="${MY_TMPDIR}test-results"

  if [ -d "$RESULTS_DIR" ]; then
 `$CP -r "$RESULTS_DIR" "$BUILD_DIR" && rm -r "$RESULTS_DIR"`
  fi
fi

exit $RETVAL

RunIPhoneSecurityd.sh
#!/bin/sh

set -e
set -u

export DYLD_ROOT_PATH="$1"
export IPHONE_SIMULATOR_ROOT="$1"
export CFFIXED_USER_HOME="$2"

"$IPHONE_SIMULATOR_ROOT"/usr/libexec/securityd

12. Add Cocoapods to the rest of your project see here: http://nscookbook.com/2013/03/recipe-18-unit-testing-with-ghunit-cocoapods/. Your project settings should look something like this:



13. In XCode 4.6.2 I had to change my AppDelegate.m file to this to get everything to the code below to get everything to run correctly.

AppDelegate.m
//
//  main.m
//  LuaUnitTests
//
//  Created by Kurry Tran on 10/15/12.
//  Copyright (c) 2012 Lua Technologies. All rights reserved.
//
#import 
#import 

int main(int argc, char *argv[])
{
  int retVal;
  @autoreleasepool {
    if (getenv("GHUNIT_CLI")) {
      retVal = [GHTestRunner run];
    } else {
      retVal = UIApplicationMain(argc, argv, nil, @"GHUnitIOSAppDelegate");
    }
  }
  return retVal;
}
Note: This blog article was very short, but I hope it was helpful.