iOS + XCode 4 + GHUnit = Mobile TDD+Continuous testing part 2 of n

Robert van Loghem

Last time I explained why I think doing TDD for mobile is imperative, and why I do it. But now it's time to get technical, and explain to you how to set up, GHUnit in XCode 4 and run unit tests, not only in the iPhone and iPad simulator but also on your own physical device!, it's in text and images but also in video form on YouTube.

Note, if you want to know why i chose GHUnit over OCUnit, just scroll down to the end of the post.

But wait….

Before I begin, I want to make one thing very clear, the difference between code unit testing and UI testing. Unfortunately, UI development can be hard to do in a TDD fashion. Especially when you want to test UI components. e.g. When I send a TouchEvent will the View respond and trigger my method in my controller.
My advice; don't do UI testing with a Unit testing framework (OCUnit, JUnit, GHUnit), do it with for example the iOS automation API, which has been created specifically to test those UI components. I'll also get back to you in a later post on how to do UI testing on Android.

What do you test with Unit testing frameworks? Well you test just the code, nothing more. Model and Controller code, not the View! You might need the help of a mocking framework to make it testable, because the view is missing and needs to be wired up for the controller and model to work properly, or it might need other controllers etc..

Let's begin setting up!

With that in mind, let's set up our own iPhone XCode 4 project! add GHunit, create a test and run it in the simulator or your own iOS device, iPhone, iPod Touch or iPad.

1. First of all download the GHUnitIOS version (0.4.28 at this time) at https://github.com/gabriel/gh-unit/archives/master
2. Unpack the downloaded zip file somewhere in your home directory, you should get the GHUnitIOS.framework directory

NOTE; I first placed it in /Developer/Library/Frameworks but XCode 4 didn't like it and when compiling it could not find the header files, therefore I placed them somewhere in my home directory (e.g. /Users/rvanloghem/Development/Frameworks/GHUnitIOS.framework)

Right, you are now ready to set up your GHUnit ready XCode 4 project.

3. For example purposes, I'm choosing a normal, navigation-based application but you might have an existing project.
- NOTE; Don't check the Include Unit Tests, because we are going to supply our own unit testing framework and not rely on OCUnit, which is supplied by default in XCode

4. In your XCode project settings (blue root icon in the tree browser) add a Target which you can call Tests, i usually base the project on a simple View-based Application (a Target named Tests will be added + a folder named Tests with all the Tests target files in them)

5. Next go back to the Tests target (click on the Tests Target) and add the GHUnitIOS.framework which you downloaded and unpacked in step 1 and 2. (click on the Build Phases tab, open up the Link Binary with Libraries, hit the + button, click Add Other and navigate+select the GHUnitIOS.framework directory on your filesystem)
6. Optional, but nice, move the GHUnitIOS.framework in your Tree to the Frameworks folder, to tidy things up

7. Set the -ObjC and -all_load in the other linker flags on the Tests Target (Select the Tests Target, select the Build Settings, search for other linker flags, and add the 2 flags)

8. Now you can delete some files which are not necessary, all the files in the Tests folder. (Note, Not! the Supporting Files folder as well, just the files!)
9. Delete the main.m file in the Supporting Files folder

10. In the Tests-Info.plist file (again, in the Supporting Files folder), clear out the Main nib file base name value

Time to create the GHUnit test runner, which will scan for our Unit Test Cases and run them.

11. Create an Objective-C class in the Tests folder named GHUnitIOSTestMain and make sure it is only! added to the Tests target

12. You can delete the GHUnitIOSTestMain.h file
13. Copy and paste source code from (http://github.com/gabriel/gh-unit/blob/master/Project-IPhone/GHUnitIOSTestMain.m) in your GHUnitIOSTestMain.m file

And now it's time to create our own test case which will fail 😉

14. Again create an Objective-C class in the Tests folder and as this is an example, name it ExampleTest, also make sure it is added to the Tests Target only!
15. You can delete the ExampleTest.h file
15. Copy and paste the next piece of code which is 99% copy/pasted from the example code from GHUnit (http://gabriel.github.com/gh-unit/_examples.html)

// For iOS
#import <GHUnitIOS/GHUnit.h> 
// For Mac OS X
//#import <GHUnit/GHUnit.h>

@interface ExampleTest : GHTestCase { }
@end

@implementation ExampleTest

- (BOOL)shouldRunOnMainThread {
    // By default NO, but if you have a UI test or test dependent on running on the main thread return YES
    return NO;
}

- (void)setUpClass {
    // Run at start of all tests in the class
}

- (void)tearDownClass {
    // Run at end of all tests in the class
}

- (void)setUp {
    // Run before each test method
}

- (void)tearDown {
    // Run after each test method
}  

- (void)testFoo {       
    NSString *a = @"foo";
    GHTestLog(@"I can log to the GHUnit test console: %@", a);
    
    // Assert a is not NULL, with no custom error description
    GHAssertNotNULL(a, nil);
    
    // Assert equal objects, add custom error description
    NSString *b = @"bar";
    GHAssertEqualObjects(a, b, @"A custom error message. a should be equal to: %@.", b);
}

- (void)testBar {
    GHAssertTrue(TRUE, @"Yes it worked");
}

@end

Right, ready to run!

16. Launch your Tests target (iTunes-like play button arrow) and run it against the simulator-scheme and your Unit test app should start

17. Now in the app in the simulator hit the blue run button and your tests will execute. (and testFoo will fail!, you can click on it to see why it failed)

And now it's time to fix it...

18. Change the NSString *b = @"bar"; in the testFoo method to NSString *b = @"foo";
19. Run the Test app again and re-run the test and they should be green or in this case black, which means your tests are ok

Showing the power of GHUnit

20. Run the app against your own iOS device-scheme. (Select iOS-device-scheme and click the iTunes like run button)

Why i chose GHUnit over OCUnit

IMHO, this shows the real power of the GHUnit testing framework, not only does it run in the simulator but it also runs on your own iPhone, iPad, etc. Whereas OCUnit can only run as part of your build on your own machine, not on your phone and not in the simulator, this for me, is a big dealbreaker.
The closer you can get your unit tests running against a real-world environment the better it will be. Why? Because you are making use of the real devices processor (not the intel x86), real memory management, or lack there-of, the real API's etc. If my Unit tests run on my phone, i'm 99.999% certain that the code under test will actually run on, yes, you guessed it, my phone.

There is of-course a downside to GHUnit, OCUnit (bundled with XCode) can be run automatically prior to you compiling your own app, this makes getting feedback about regression a lot faster, GHUnit is something which you have to run manually, in this case. But to solve that problem, or at least make it a whole lot better, we can use continuous integration aka build server to do the auto-running-of-unit-tests for us. There is a very nice blog post which compares various iOS unit testing frameworks.

So what is next? Well, the topic for my next blog and video in this series is hooking up a XCode project + GHUnit to Jenkins (or Hudson for the Oracle minded people out there).

Comments (18)

  1. Dave Crane - Reply

    March 30, 2011 at 4:15 pm

    Excellent tutorial on GHUnit! Thank your very much.

  2. Richard - Reply

    April 6, 2011 at 1:51 pm

    Thanks!!! I was just looking for a unit-testing framework for Xcode!!

  3. Mark - Reply

    May 26, 2011 at 2:32 am

    Robert,

    Unfortunately I didn't find your post before I wrote one last month discussing OCUnit vs. GHUnit. My conclusion was that as of Xcode 4, OCUnit has gotten good enough that I'm a fan - I'll likely start using OCUnit.

    My article did not mention the very clever point you've made, though -- which is that OCUnit's not going to run on the device. Well-taken, and I'd like to add that, along with attribution, to my article.

    You may also be interested in the post I wrote about getting Jenkins to work with OCUnit & application tests (not only logic).

    http://longweekendmobile.com/2011/04/15/unit-testing-in-xcode-4-use-ocunit-and-sentest-instead-of-ghunit/

    Cheers

    Mark

  4. [...] yet, so it’s not really fair to judge. However, fellow iOS dev Robert has. See his article about setting up GHUnit with Xcode 4. He even has a sexy [...]

  5. Dominik Pich - Reply

    July 5, 2011 at 12:27 am

    Hi,
    i incorporated GHUnit into Xcode XXbeta2 on osx XXgm and it works fine for iOS XXb2 and osx XXgm

    I followed your great article

    now Id like to proceed to make it run headless and in jenkins. (same as you)
    I followed the docs and it does work ok for machos

    But it fails for iOS XX

    Any help / tip / hint?

    Regards and thanks for an awesome article that helped me quite a lot
    Dominik

    -----
    The console output follows:

    BUILD xyKit-iOS OF PROJECT xyKit WITH CONFIGURATION Debug AND SDK x.0===
    Check dependencies

    ...

    PhaseScriptExecution "Run Script" build/xy.build/Debug-iphonesimulator/xy-iOSTests.build/Script-B4F8CF2A13C245C50000F3D6.sh
    cd /Users/dominik/Desktop/preliminary_project_structure/Framework
    /bin/sh -c /Users/dominik/Desktop/preliminary_project_structure/Framework/build/xyKit.build/Debug-iphonesimulator/xy-iOSTests.build/Script-B4F8CF2A13C245C50000F3D6.sh
    Running: "/Users/dominik/Desktop/preliminary_project_structure/Framework/build/Debug-iphonesimulator/xy-iOSTests.app/xyKit-iOSTests" -RegisterForSystemEvents
    2011-07-05 00:02:05.237 xyKit-iOSTests[370:ef03] Warning: CFFIXED_USER_HOME is not set! It should be set to the simulated home directory.
    2011-07-05 00:02:05.274 xyKit-iOSTests[370:ef03] SBSetAccelerometerClientEventsEnabled failed: (ipc/send) invalid destination port

  6. Robert van Loghem - Reply

    July 5, 2011 at 5:36 pm

    Hi Dominik,

    I'm currently working on a new blog post, which sums up what I've done in my latest youtube video (http://www.youtube.com/watch?v=6ycxFcIPhQg), which is exactly what you are trying to do, run ghunit in jenkins.

    What I haven't tried so far, is try it with the latest xcode version. I only tried it in version 4.0.x

    cheers,
    Robert

  7. Aditi - Reply

    August 30, 2011 at 10:44 am

    This is an excellent tutorial...i was struggling with GHUnit for about two weeks now... especially when the MAc and the Xcode got updated... it was almost like a boon to have found this tutorial! Thank you so much!

  8. Ajay Rawat - Reply

    September 16, 2011 at 12:47 pm

    Thanks a lot buddy, Its help me a lot to configure my project with GHUnit.

  9. Rutger van Dijk - Reply

    November 5, 2011 at 11:24 am

    Hi Robert,

    Thanks for the tutorial, but unfortunately I can't get it to work. First of all, I don't have a file name 'main.m' in my Test folder.

    Next, when I move the framework to the Framework folder, the specified framework turns red in the Test target 'Link with libraries'.

    Also, it seems that my Xcode 4 (4.1) seems to skip tests when I press 'Test' / 'Run'.

    To give you an example:

    - (void)testExample
    {
    STFail(@"First test fails.");
    }

    - (void)testSecondExample
    {
    NSString *myString = @"string";
    STAssertTrue([myString isEqualToString:@"string"], @"Second test fails.");
    }

    The first one is run and fails. But the second one doesn't run at all. According to the console log.

    Any thoughts on this ? I would really like to create some unit tests, but at the moment Xcode is giving me more pain in the head than it should.

    Thanks!

    Rutger

    • Robert van Loghem - Reply

      November 7, 2011 at 2:28 pm

      Hey Rutger,

      That is weird, so when you duplicated the Target you didn't get a main.m file?

      The 2nd thing can be solved easily by adding (dragging) the library to the 'Link with libraries' in your target

      As for the third thing, how did you create the test? Because you seem to be using OCUnit instead of GHUnit (although, GHUnit can run SenTesting tests). Did you use the Xcode wizard for creating the test itself?

  10. rUpAs - Reply

    November 15, 2011 at 10:33 am

    Hi Robert,

    Thank you very much for the beautiful article. The level of details you captured is very impressive.

    But i faced few problems and need your help in solving the below problem.

    I tried the same steps with xcode4.0 and xcode4.2. In both the places, after compiling everything and when i try to run as of step #17, the following error is observed

    Undefined symbols for architecture i386:
    "_main", referenced from:
    start in crt1.10.6.o
    ld: symbol(s) not found for architecture i386
    collect2: ld returned 1 exit status

    Any clue what this means?

  11. rUpAs - Reply

    November 15, 2011 at 10:49 am

    Actually the following link is not available to add code to the GHUnitIOSTestMain.m

    https://github.com/gabriel/gh-unit/blob/master/Project-iOS/GHUnitIOSTestMain.m

    Can you please make it available. This should solve my problem

  12. Matti - Reply

    November 15, 2011 at 10:10 pm

    Thanks for the tutorial!

    By the way. Seems that something has been changed on XCode 4.2 and / or iOS 5 (could be my platform as well), and the docs didn't work for me. It didn't find any of the GHUnitIOS headers and the framework wasn't in the list when trying to add it to the project link library list. So, I copied the GHUnitIOS.framework to /Developer/Library/Frameworks -directory. Now it shows in the framework list. What else I did, I replaced the UIApplicationMain initialization row with:
    int retVal = UIApplicationMain(argc, argv, nil, NSStringFromClass([GHUnitIOSAppDelegate class]));

    and added:
    #import

    Now it starts without a crash.

    Cheers,
    Matti

    • Matti - Reply

      November 15, 2011 at 10:14 pm

      (For some reason the import row is broken, I imported GHUnitIOS/GHUnitIOSAppDelegate.h)

  13. Rutger van Dijk - Reply

    November 18, 2011 at 2:24 pm

    Hi!

    Thanks for your reply: i fixed it!

    Removed old target and cleanup the project (through the Organizer). Next, I followed your guide (and that of http://www.raywenderlich.com/3716/unit-testing-in-xcode-4-quick-start-guide)

    Now it works! Thanks!

  14. Vikram VI - Reply

    March 26, 2012 at 8:12 am

    Hi Robert,

    Thanks for this nice blog , last week I did setup of GHUnit with XCode 4.2.1 and could able to run sample test case.

    But currently I'm facing a major roadblock as below , can you please help me out with it
    1. Our application is written based on MVC model
    2. I'm trying to call method written in this application but always getting error message as "Lexical or Preprocessor Issue 'String' file not found. I'm not able to overcome this compilation issue."

    It'll be helpful if you can please share some real world application using GHUnit framework

    Thanks in advance and eagerly waiting for your response.

    Regards,
    Vikram

  15. Junda - Reply

    October 4, 2012 at 4:17 am

    One of the reason GHUnit is better than OCUnit is that it supports asynchronous methods or blocks!

    I have also a guide on configuring GHUnit for CocoaPods that might be useful for devs: http://samwize.com/2012/10/04/how-to-setup-ghunit-with-cocoapods/

  16. gsreddy - Reply

    November 17, 2014 at 7:31 am

    how to access/test application using GHUnit .please give me one example.

    i was calling ..like that
    MyTable *table=[[MyTable alloc]init];
    [table method];

    then i was run ...then i have a error like that
    symbol(s) not found for architecture i386

Add a Comment