My favorites | Sign in
Logo
                
Search
for
Updated May 07, 2009 by thomasvl
Labels: Phase-Deploy, Featured
iPhoneUnitTesting  
How to do iPhone unit testing

Introduction

This is a quick tutorial on doing iPhone unit testing using the facilities in the Google Toolbox For Mac. Please send mail to the group if any clarification is required.

Basic Project Setup

Hopefully your project already has an application target for building something for the iPhone.

  1. Create a new iPhone Target (Cocoa Touch Application) via "Project Menu > New Target...". Choose a name that makes some sense such as "Unit Test".
  2. Add google-toolbox-for-mac/UnitTesting/GTMIPhoneUnitTestMain.m to your target
  3. Add google-toolbox-for-mac/UnitTesting/GTMIPhoneUnitTestDelegate.m to your target
  4. Add google-toolbox-for-mac/UnitTesting/GTMIPhoneUnitTestDelegate.h to your target
  5. Add google-toolbox-for-mac/UnitTesting/GTMSenTestCase.m to your target
  6. Add google-toolbox-for-mac/UnitTesting/GTMSenTestCase.h to your target
  7. Add google-toolbox-for-mac/GTMDefines.h to your target
  8. Add a new 'run script' build phase as the last step of your target build via "Project Menu > New Build Phase > New Run Script Build Phase", and dragging it to the end of the build steps if needed.
  9. Edit your Run Script Build Phase by double clicking it, and set the shell to "/bin/sh" and the script to "PATH_TO_GTM/UnitTesting/RunIPhoneUnitTest.sh", where PATH_TO_GTM is the path to your local version of google-toolbox-for-mac.
  10. Build! Note that if you choose build and go you will see your unit tests executed twice, once as part of the build script, and once being run

Your target should now build cleanly, and if you check the build log you should see something like: "Executed 0 tests, with 0 failures (0 unexpected) in 0.001 (0.001) seconds" at the end.

Creating a unit test

  1. Add the source you want to test to your target. For example if you want to test class Foo, make sure to add Foo.h and Foo.m to your target.
  2. Add a new unit test file to your target via "File > New File" and choose "Objective-C test case class" from the "Mac OS X" Cocoa category. Call it FooTest.m, or follow whatever convention your project has for naming test classes.
  3. In FooTest.h, change #import <SenTestingKit/SenTestingKit.h> to #import "GTMSenTestCase.h"
  4. Add test cases as you normally would. See Apple's Documentation for a good tutorial on how to test in Objective C. The key is that your test case methods are declared - (void)testBar. The name must start with "test" and they must return nothing and have no arguments.

Now when you build your target you should now see test cases executing. You can repeat this process creating additional source files for each class you want to write Unittests for.

Notes

Advanced Stuff

Unit test Logging

When Unittesting is done correctly, you often have a lot of log messages logging because you are testing edge cases that you may not expect to hit in the real world very often. It's nice to be able to verify that the log messages you are receiving as you run your tests are the ones that you expect to receive. You can do this in GTM by enabling unit test logging.

  1. Assuming you are using _GTMDevLog to do your logging,#define _GTMDevLog _GTMUnittestDevLog somewhere, either in target settings, or in your prefix. If you are using NSLog you can just define it to be _GTMUnittestDevLog.
  2. Add google-toolbox-for-mac/DebugUtils/GTMDevLog.m to your target.
  3. Add google-toolbox-for-mac/UnitTesting/GTMUnitTestDevLog.m to your target.
  4. Add google-toolbox-for-mac/Foundation/GTMRegex.m to your target.
  5. You may also need to add some headers depending on your search paths

Now when you build all of the logging that you do via your unit tests will get checked to make sure that it conforms with your expectations. You set up these expectations before running your tests using [GTMUnitTestDevLog expect*] methods. See GTMUnitTestDevLog.h for more info.

UI and State Testing

GTM can also help you test your UI's representation and state.

  1. Add google-toolbox-for-mac/UnitTesting/GTMNSObject+UnitTesting.m to your target.
  2. Add google-toolbox-for-mac/UnitTesting/GTMUIKit+UnitTesting.m to your target.
  3. Add google-toolbox-for-mac/UnitTesting/GTMCALayer+UnitTesting.m to your target.
  4. Add google-toolbox-for-mac/Foundation/GTMSystemVersion.m to your target.
  5. Add the CoreGraphics and QuartzCore frameworks to your target.
  6. You may also need to add some headers depending on your search paths

Check out UnitTesting/GTMUIKit+UnitTestingTest.m or AppKit/GTMNSBezierPath+RoundRectTest.m for examples of using UIUnitTesting.

For more information on some of this, check out CodeVerificationAndUnitTesting. Hope this helps.

Unit Test Environment Variables

To encourage "bad behavior" by the code being tested, the RunIPhoneUnitTest.sh script sets a variety of environment variables. If you are wondering why the unit tests fail when you are building, but don't fail when you are running, it may be because of a side effect of one of these variables. Take a look at all the export commands within RunIPhoneUnitTest.sh to see what's actually going on.

Leaks

By default the iPhone unit tests will run leaks after all the tests have completed. This can be turned off by setting the GTM_DISABLE_LEAKS environment variable before you execute the RunIPhoneUnitTest.sh script. Out of the box, NSZombies will also be enabled. This however interferes slightly with leaks and makes it difficult to get good backtraces and context. If you want the backtraces and context, set the GTM_DISABLE_ZOMBIES environment variable before you execute the RunIPhoneUnitTest.sh script. All leaks will appear as warnings on the build console.

Termination

Some of Apple's tools (such as Instruments) don't want the app to terminate underneath them. By default the iPhone unit test app will terminate when it has finished it's run. Set the GTM_DISABLE_TERMINATION environment variable if you want to disable termination and just have the unit test "run" until you are done with it.


Comment by dujunfly, Jul 26, 2008

GTMIPhoneUnitTestDelegate.h is needed too.

Comment by dmaclach, Jul 28, 2008

Done. Thanks dujunfly

Comment by czgarrett, Aug 30, 2008

List of the macros added by GTMSenTestCase.h:

STAssertNoErr(a1, description, ...) STAssertErr(a1, a2, description, ...) STAssertNotNULL(a1, description, ...) STAssertNULL(a1, description, ...) STAssertNotEquals(a1, a2, description, ...) STAssertNotEqualObjects(a1, a2, desc, ...) STAssertOperation(a1, a2, op, description, ...) STAssertGreaterThan(a1, a2, description, ...) STAssertLessThan(a1, a2, description, ...) STAssertLessThanOrEqual(a1, a2, description, ...) STAssertEqualStrings(a1, a2, description, ...) STAssertNotEqualStrings(a1, a2, description, ...) STAssertEqualCStrings(a1, a2, description, ...) STAssertNotEqualCStrings(a1, a2, description, ...) STAssertEqualObjects(a1, a2, description, ...) STAssertEquals(a1, a2, description, ...) STAssertEqualsWithAccuracy(a1, a2, accuracy, description, ...) STAssertTrueNoThrow(expr, description, ...) STAssertFalseNoThrow(expr, description, ...) STAssertThrows(expr, description, ...) STAssertThrowsSpecific(expr, specificException, description, ...) STAssertThrowsSpecificNamed(expr, specificException, aName, description, ...) STAssertNoThrow(expr, description, ...) STAssertNoThrowSpecific(expr, specificException, description, ...) STAssertNoThrowSpecificNamed(expr, specificException, aName, description, ...)

Comment by z...@wzph.com, Sep 15, 2008

Everything was building for me, but I could not see the test output, and I was getting the following in the build results window:

syntax error near unexpected token `newline' `!<arch>'

The problem was that I had created a build target of type Cocoa Touch Static Library. I deleted it, created a Cocoa Touch Application target, and everything worked.

Comment by clifton.craig, Oct 12, 2008

Using a project that I had already been unit testing with rbiPhoneTest, I added the test files as indicated above. However, I get 170 errors, all referencing AppKit?. I see no references to AppKit? in any of the files I pulled in from GTM. What am I doing wrong?

Comment by clifton.craig, Oct 12, 2008

Can I take back that stoopid comment I just left above? Selecting "Cocoa Application" under the Mac group instead of "Cocoa Touch Application" under the iPhone group will lead to the above id10t error.

Comment by abushnaq, Oct 15, 2008

GTMNSBezierPath+RoundRectTest?.m is under AppKit? rather than UnitTesting?(took me some time to find it).

Comment by dmaclach, Nov 15, 2008

abushnaq, I updated the docs with a bit more path context for you.

Comment by stefeo, Nov 28, 2008

THANK YOU! works like a charm and is basically saving my life. Question: Tutorial says: 'In FooTest?.h, change #import <SenTestingKit?/SenTestingKit?.h> to #import "GTMSenTestCase.h"' Is it also required to change the subclass to GTMSenTestCase? I tried it and didn't notice any difference, but I'd rather do the correct thing to avoid any quirks later.

Comment by carl.veazey, Dec 15, 2008

Does this work on the device as well? Whenever I try to run it I get an error "bad CPU type in exectuable"

Comment by mlooney, Dec 23, 2008

Current script seems to run the test suites twice for some reason...

Comment by derrick.schneider, Dec 24, 2008

I'm seeing an odd error from the shell script: /Users/derrick/Downloads/google-toolbox-for-mac-1-5-1/UnitTesting?/RunIPhoneUnitTest.sh: line 23: 6836 Bus error "$TARGET_BUILD_DIR/$EXECUTABLE_PATH" -RegisterForSystemEvents?

Love that you all put this out there. I had built a separate Mac executable that invoked unit tests on my Foundation-only services and business layer, but I will much prefer a wider range of testability.

Comment by tcstellanova, Jan 03, 2009

AppKit?/GTMNSBezierPath+RoundRectTest?.m doesn't seem to apply to iPhone UI testing. It utilizes GTMAssertDrawingEqualToImageNamed which is only defined in GTMAppKit+UnitTesting?.h

Maybe use GTMAssertObjectImageEqualToImageNamed and GTMAssertObjectStateEqualToStateNamed ?

Comment by bonczek, Feb 12, 2009

Does anyone know how to run these test cases in a GDB environment? I would love to break on exceptions if I could.

Comment by frederic.vergez, Mar 19, 2009

+1 on last remark, how to debug test cases ?

Comment by aroonpahwa, Mar 27, 2009

I'm guessing there isn't a way to debug the test cases because of the way the whole thing is built right now. We're building a target application and then running it as a build event. Debugging usually starts AFTER building...but our tests are being run during the build...

Wish that wasn't the case. May be someone would like to convert the shell script to an objective c function and then we can call that in the test application's appdelegate and thus be able to debug the tests.

Comment by itsari7, Mar 28, 2009

i followed the 10 wiki steps above and got the error: error: cannot find interface declaration for 'GTMSenTestCase', superclass of 'My_Test?'

I noticed there is no GTMSenTestCase declared in GTMSenTestCase.h/.m?

Thanks for your help

Comment by rmd6502, Apr 13, 2009

After only 24 hours I have done a refactor and removed several bugs and hacks from my code! I am getting the problem that derrick.schneider reported, and have to click a "continue" button through the error.

Comment by sbwoodside, Apr 26, 2009

You do not need to add any of the headers (.h) files (GTMIPhoneUnitTestDelegate.h, GTMSenTestCase.h or GTMDefines.h) to the new target. Targets don't normally contain .h files directly anyway, and the usual check-box isn't available for them. Adding them just makes XCode spit out a warning on compile, and taking them out doesn't hurt anything. Also, you can simplify adding files to target by right-clicking on the Groups & Files header (on the left) and selecting Target Membership. Remember that this changes when you switch targets.

For myself, building never kills the iPhone simulator, which I know is going to be a PITA. Where in the source code is this supposed to happen? It's not obvious.

Comment by sbwoodside, Apr 26, 2009

I've got a couple more issues:

1. How do I use this when there's delegates at play? With callbacks? I have an object that does an async NSURLConnection and I want my unit test to wait for the callback and check if the value is correct.

2. When I try to test a code path that involves a .cpp file, it totally blows up.

Comment by robwhitener, Apr 27, 2009

I think I have a stupid question: I have two model objects I want to test, so I decided to create two separate test case classes (in two separate files). Am I supposed to lump all of my tests into one file? I see that the tests from my first class get loaded and executed, but my new ones don't.

Comment by bdomokos74, May 02, 2009

Have you added the second test to the unittest target?

Comment by refuxx, May 07, 2009

+1 On debug unit test cases, that would be wonderful!

Comment by bkliewer, May 11, 2009

As a (primarily) Java programmer, I just want to underscore itsari7's comment by pointing out that you must use

@interface FooTest : GTMTestCase {

not

@interface FooTest : GTMSenTestCase {

because GTM*Sen*TestCase?.m defines the class GTMTestCase (i.e., the source file name does not match the class name).

Comment by Jonathan.Arbogast, May 18, 2009

I'm seeing two build errors for each test that fails. Has anyone else experienced this? Is this expected for some reason?

I'm being careful to just Build my unit tests not Build and Go but I still get duplicate failures.

Comment by g.castaldi, May 19, 2009

It seems that isn't possibile define private methods using Categories with iPhoneUnitTesting (probably the problem is in OCUnit). If I define a category in the following manner in MyCategory??.m the compiler says "error: syntax error before token '{'"

@interface MyCategory? () {

    - (NSMutableData ) myPrivateMethod: (NSString ) string; 

}

There is the problem only with the target for tests.

Any solution?

Comment by Jonathan.Arbogast, May 19, 2009

You are simply incorrectly defining a category. Lose the curly braces and add an @end directive. You have a method definition inside of a block where instance variables are supposed to be declared.

Comment by nraychaudhuri, Jul 03 (41 hours ago)

If you having problem running the unit test target with 3.0 iphone simulator, check the following link out http://code.google.com/p/google-toolbox-for-mac/issues/detail?id=17


Sign in to add a comment