Setting up travis-ci for automated unit testing of iOS projects on GitHub

This guide is how I set up new projects to run unit tests automatically when I push a commit or merge a branch on GitHub for iOS projects.

The first step is to create a new project in Xcode. I am going to select a single view application and enable storyboards, Automatic Reference Counting and Unit Tests.

Screen Shot 2013-05-06 at 11.42.27 PM

All I’m going to change is the testExample method in the unit testing bundle to something that will pass. For now I have just gone with this:

- (void)testExample
{
    STAssertNil(nil, @"This object should be nil");
}

Check that the tests pass by hitting cmd+u. Now that I have my unit tests passing, I’m going to add this project to GitHub. I’ll leave the details out here, as this isn’t the focus of this tutorial but you can find my example project here: https://github.com/daniel-beard/SettingUpTravisCIForiOS.

Now we can setup the TravisCI build. I use the xctool to build my projects as it has a nicer output and is easier to use than the built in xcodebuild tool. Add xctool as a submodule to the git repository using the following commands:

git submodule add https://github.com/facebook/xctool.git ./xctool
git submodule update --init
git commit ./xctool -m "Added xctool as a submodule"

Then we need to add a config file so that travis-ci knows how to build our project. This file is named .travis.yml and lives in the root of the git repository. Here is the contents of mine:

language: objective-c
before_install: "git submodule init && git submodule update && sudo gem update --system && sudo gem install bundler && bundle install"
script: "bundle exec rake test --trace"

Then we have to add a Rakefile that tells the xctool which project and target to build:

desc 'Run the tests'
task :test do
   exec('xctool/xctool.sh -project SettingUpTravisCIForiOS.xcodeproj -scheme SettingUpTravisCIForiOS test')
end

task :default => :test

And finally the Gemfile

source 'https://rubygems.org'

gem 'rake'

You can then test locally that your project builds using the command rake in your root git repository.
I get build messages, then ** TEST SUCCEEDED: 1 of 1 tests passed ** (25285 ms). Now that we have the unit tests running locally, all that is left is to set up the travis-ci build. Login to https://travis-ci.org/ with your GitHub account and under account settings select the repository that should be unit tested automatically. This automatically sets up a service hook in GitHub so that every time you push to your repository, it will get unit tested.

You can also use the status images from travis-ci to show the test status directly in your README file on GitHub. Check out my example project here: SettingUpTravisCIForiOS

Advertisements

Developing Xcode 4 Plugins

This is a quick guide that documents how to start developing plugins for Xcode4. You need to have Xcode installed to create plugins.

Step 1 – Xcode Plugin Project Template

  • Grab the Xcode project template for creating plugins from here
  • Create the plugin template folder ~/Library/Developer/Xcode/Templates/Project Templates/Application Plug-in/Xcode4 Plugin.xctemplate if it doesn’t already exist.
  • A quick way to do this is with the following command mkdir -p "~/Library/Developer/Xcode/Templates/Project Templates/Application Plug-in/Xcode4 Plugin.xctemplate"
  • Copy the contents of the GitHub repository to the folder you just created.
  • Restart Xcode.

Step 2 – Create a test project

  • Open Xcode, and select File > New > Project
  • Then under OS X > Templates tap Xcode4 Plugin (shown below)

Screen Shot 2013-04-29 at 3.44.05 PM

From the GitHub repo:

The default plugin file links against AppKit and Foundation, and, when built 
(and Xcode is restarted), creates a menu item labeled "Do Action" in the File menu. 
Pressing the menu item should open an alert. Customize at will!

If we run the project we just created, it will automatically build and copy the plugin to the right location. In this case it is ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/[Project Name].xcplugin. Restarting Xcode, we get a new menu item under the File menu!

Screen Shot 2013-04-29 at 3.53.02 PM

When we click on the menu item, we get an alert:

Screen Shot 2013-04-29 at 3.53.49 PM

Notes:

  • Xcode plugins have to be written using Objective-C GC, this means you have to use retain and release calls in your code. (No ARC support).

Where to go from here?

  • This StackOverflow answer has some great ideas on where to go next.
  • You can get a dump of the private headers that Xcode uses by using the class-dump tool
  • brew install class-dump is the quickest way to get it if you have homebrew installed.
  • IDEKit and IDEFoundation are present at Xcode.app/Contents/Frameworks
  • DVTKit and DVTFoundation are present at Xcode.app/Contents/SharedFrameworks
  • By registering an observer for nil you can see all the notifications that are being called. This is useful to find out which actions are called and when, and what notifications you might need to listen for.

Objective-C Runtime

It is pretty rare to actually have to dive into the objc-runtime for any day to day coding. Most developers wont have to touch the runtime, however it is helpful to know what is possible and be able to use it if required. The objective-c runtime is written in C and is how the underlying parts of the objective-c language work including message sending, ivars and properties. This post shows an example of where I have used the Objective-C runtime in one of my projects.

One example of where I have used the runtime in my projects is the validation code in DBValidator. The validation code is implemented as a category on NSObject called NSObject+DBValidator. This is so we can add validation rules to any objective-c object. The only problem with this approach is that you can’t add any properties or ivars to an object using a category.

We can work around this limitation by using the objective-c runtime directly.

Below is the implementation of the NSObject+DBValidator category:

#import "NSObject+DBValidator.h"
#import <objc/runtime.h>

#define VALIDATION_RULES_KEY @"validationruleskey"

@implementation NSObject (DBValidator)

-(void) setValidationRules:(NSMutableArray *)validationRules {
    objc_setAssociatedObject(self, VALIDATION_RULES_KEY, validationRules, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-(NSMutableArray*) validationRules {
    NSMutableArray *validationRules = objc_getAssociatedObject(self, VALIDATION_RULES_KEY);
    if (!validationRules)
        validationRules = [NSMutableArray array];
    return validationRules;
}

-(void) addValidationRule: (DBValidationRule*) validationRule {

    NSMutableArray *validationRules = self.validationRules;
    if (validationRule)
        [validationRules addObject:validationRule];
    self.validationRules = validationRules;
}

-(void) removeAllValidationRules {
    NSMutableArray *validationRules = self.validationRules;
    [validationRules removeAllObjects];
    self.validationRules = validationRules;
}

-(NSMutableArray*) validate {
    NSMutableArray *failureMessages = [NSMutableArray array];
    for (DBValidationRule *rule in self.validationRules) {
        BOOL isValid = [rule passesValidation];
        if (!isValid)
             [failureMessages addObject: rule.failureMessage];
    }
    return failureMessages;
}

@end

We have a @property defined in the header called validationRules and we override both the setter and getter in the implementation. In the setValidationRules: method we use a C function from the objective-c runtime called objc_setAssociatedObject. This function allows us to set a reference on the self object. We give it a key, the object (in this case the validationRules passed to the method) and the association policy.

The valid options for the association policy are:

enum {
   OBJC_ASSOCIATION_ASSIGN = 0,
   OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1,
   OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
   OBJC_ASSOCIATION_RETAIN = 01401,
   OBJC_ASSOCIATION_COPY = 01403
};

Notice how these options map directly to @property storage options! We are using OBJC_ASSOCIATION_RETAIN_NONATOMIC because we want our NSObject to retain the validation rules that are set on it.

In our validationRules method, we use a similar call from the objective-c runtime called objc_getAssociatedObject. This allows us to retrieve the object we set a reference to in the preious method. We have to pass the parent object and the key for the associated object we want. We return an empty array if validation rules are not yet set for this object.

Check out the full source code in the DBValidator GitHub Project

Git notes

This is a list of git notes that I use everyday. Hopefully someone else finds it useful. I will update this list over time.

  • The master branch reflects production ready code only. This branch should be deployable at any time and merges to this branch should not break the build process.
  • Feature branches should be merged into the master when they are complete.
  • Feature branches consist of a single feature or discrete unit of work
  • Commit early and commit often!

Generating SSH-Public Key

  1. Switch to your .ssh directory and check if the id_rsa.pub file exists, this is your public key
  2. If it doesn’t exist, generate one with the following commandssh-keygen -t rsa -C "youremail@email.com"Output is something like the following:
    Generating public/private rsa key pair.
    Enter file in which to save the key (/Users/danielbeard/.ssh/id_rsa): 
    Enter passphrase (empty for no passphrase): 
    Enter same passphrase again: 
    Your identification has been saved in /Users/danielbeard/.ssh/id_rsa.
    Your public key has been saved in /Users/danielbeard/.ssh/id_rsa.pub.
    The key fingerprint is:
    43:c5:5b:5h:b1:f1:51:43:ad:20:a6:92:6a:1f:8a:3a danielbeard@iosdev.local
    

Copy the contents of the public key file to your clipboard:
pbcopy < ~/.ssh/id_rsa.pub

Normal Workflow

  • Checkout the remote repository with: git clone git@github.com:AustralianFinanceGroup/SalesTools.git
  • Updating the repository (won’t need to be done the first time): git pull
  • Create a new branch from the master or current development branch git checkout -b featurebranchname master
  • Make changes to local files
  • Check local modifications with git status
  • Stage certain files with git add <file1> <file2> etc or add all changes git add .
  • Commit to you local repository git commit -m 'commit message'
  • Push the local commit to the remote repository and set up for tracking git push -u origin featurebranchname
  • Repeat until feature branch is complete.
  • Merge master or development branch into the working branch
    • git checkout master
    • git pull
    • git checkout featurebranchname
    • git merge master
  • Push to remote repository git push -u origin featurebranchname
  • Submit pull request and wait for code review

Repository Changes

  • Checking which files are in what state: git status
  • Tracking new files: git add README
  • Committing your changes: git commit -m 'Commit message goes here'
  • Push your current master to the remote origin: git push -u origin master
  • Unstaging a staged file: git reset HEAD <file>
  • Stage all untracked files – git add -u
  • Unmodifying a modified file: git checkout -- benchmarks.rb

Notes:

  • The git add command stages a file for a commit. Calling git status shows which files are staged/unstaged or if they are untracked.
  • If you modify a file after calling git add, the git status command will show the file as being both staged and unstaged.
  • If you modify a file after calling git add, you have to run git add again to stage the latest version of the file
    Providing the -a option to git commit makes git automatically stage every file before doing the commit. (Letting you skip the git add part)

Basic repository changes: http://git-scm.com/book/en/Git-Basics-Recording-Changes-to-the-Repository

Branching

  • Creating a new branch: git checkout -b branchname
  • Branching from a current branch: git checkout -b feature devbranch
  • Pushing branch to remote with tracking: git push -u origin branchname
  • Rename branch git branch -m <oldname> <newname>
  • Delete the local branch: git branch -d branchname
  • Delete the remote branch: git push origin :branchname
  • Listing all branches: git branch -a
  • Just list remote branches: git branch -r
  • Checking out a tracked remote branch: git checkout --track origin/branch_name
  • Merging a branch
    • Must have committed all changes (at least locally) like so: git commit -a -m "Made a change in this branch"
    • Switch to whichever branch you are merging back into, e.g. master git checkout master
    • Merge the branch (ALWAYS USE NO-FF): git merge --no-ff branchname

Notes:

Tracked branches are local branches that have a direct relationship to a remote branch.
If you’re on a tracking branch and type git push or git pull, git automatically knows which branch and server to push/pull from.

Git remote branches :http://git-scm.com/book/en/Git-Branching-Remote-Branches#Tracking-Branches
Git local branches: http://git-scm.com/book/en/Git-Branching-Basic-Branching-and-Merging

Tagging

  • Only used for tagging master branch merges or “releases”
  • You can tag at any point, it doesn’t have to be immediately.
  • Show the commit history like this: git log --pretty=oneline
15027957951b64cf874c3557a0f3547bd83b3ff6 Merge branch 'experiment'
a6b4c97498bd301d84096da251c98a07c7723e65 beginning write support
0d52aaab4479697da7686c15f77a3d64d9165190 one more thing
6d52a271eda8725415634dd79daabbc4d9b6008e Merge branch 'experiment'
0b7434d86859cc7b8c3d5e1dddfed66ff742fcbc added a commit function
4682c3261057305bdd616e23b64b0857d832627b added a todo file
166ae0c4d3f420721acbb115cc33848dfcc2121a started write support
9fceb02d0ae598e95dc970b74767f19372d61af8 updated rakefile
964f16d36dfccde844893cac5b347e7b3d44abbc commit the todo
8a5cbc430f1a9c3d00faaeffd07798508422908a updated readme
  • To tag a certain commit, specify the commit checksum (or part of it) at the end of the command git tag -a v1.2.1 9fceb02
  • Then to transfer all your tags to the remote repository git push origin --tags

Git tags: http://git-scm.com/book/en/Git-Basics-Tagging

Tracking changes between branches

  • Show the modified files between two branches: git diff --name-status master..branch
  • Show side by side comparison : git difftool -t vimdiff master..branch

Other notes and links

Git aliases: http://git-scm.com/book/en/Git-Basics-Tips-and-Tricks#Git-Aliases
Branching model: http://nvie.com/posts/a-successful-git-branching-model/
GitHub flow: http://scottchacon.com/2011/08/31/github-flow.html

Github Gist Support for WordPress

Something I didn’t realise was possible until now, is WordPress natively supports Github Gists. All you have to do to embed a gist in your post is copy and paste the gist url on a new line. Everything else is handled automatically!
Code formatting is something that has been difficult for me up until now, any posts from now on will definitely be using gists.

More info here: http://en.support.wordpress.com/gist/

Example: