My First Cocoa App

Pat McGee

Copyright 2008 James Patrick McGee. Email: JPM at XorAndOr dot com. This work is licensed under the Creative Commons Attribution-Share Alike 3.0 Unported License. To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/ or send a letter to Creative Commons, 171 Second Street, Suite 300, San Francisco, California, 94105, USA.

Abstract

Here are instructions for creating a simple Cocoa App using Xcode 3.1, git, SenTestingKit, and OCMock.

Work in progress

I'm still writing this because I haven't solved all the problems I need to solve. Anytime you see something marked with "+++", this signals a question that I haven't answered yet. If you know the answer, please email me at the address given above. I'll update the document. Please let me know if you want me to credit you in the revision.

Introduction

I've programmed for decades, working on many, many systems. I even worked on NeXTStep 0.9 and 1.0, back in the day. But, except for one NeXTStep program years ago that I didn't finish because the project got cancelled, I've never written a GUI program. I've written lots of command line programs and socket filters and SQL scripts, and embedded systems where I had to write what passed for the operating system and all the device drivers. I've written programs in thirty or so different programming languages. I've designed many GUI programs that other people implemented. I finally got frustrated and decided to put all those things together and write a complete GUI application of my very own.

I wanted to integrate this with a good content tracking system, a good unit test framework, and a good mock object assistant. For various reasons, I chose Objective-C, Xcode 3.1, git, SenTestingKit, and OCMock.

I decided that the first thing I would do would be to write a very simple application that didn't accomplish much, but that demonstrated how to use every one of those technologies. What follows is my instructions for getting from a standing start to having a small application, and having used each important tool at least once.

In the rest of this document, I try to include three levels of detail for everything. The top level, which I put in the section heads, should be good enough for experts who just need a checklist or a reminder. I put the second level in the first or first few paragraphs of each section. I put really detailed steps after that. If you can do the task without looking at this level of detail, feel free to skip it.

Note: when explaining git, I assume that you're able to use Terminal and vim. If you aren't yet, you should be. Go practice. There are GUI versions of these tools, but I'm not yet conversant with them.

Background on git

You will have four different places where git stores your files. First is the working directory, where you make your edits. Next is a cache in which git stores copies of your files as you move them between the working directory and the repository. A file stored in the cache isn't connected with any particular version or tag or directory tree. Then there's your private repository, in which a file is linked to the entire rest of the project and the history and the tags. Finally, there's the public repository.

You cannot move a file directly from the working directory into a repository. You must stage it through the cache first. Some git commands hide this staging action from you; that doesn't mean it doesn't happen. You'll get confused if you forget it. Also, you cannot move a file from the cache into the public repository. You must stage it through your private repository.

So, here's where files can be moved between:

working <=> cache <=> private repository <=> public repository.

You can't skip a step.

(Don't get confused by the fact that git uses the same physical implementation, called the index, for both the cache and the repository. The cache and the repository are two different conceptual structures, and are definitely not interchangeable. If you haven't read enough documentation to have seen a mention of the index, ignore this note.)

Get and install the tools

Start by downloading Xcode 3.1 from the Apple Developer Connection site: http://developer.apple.com/tools/xcode/ , git from git.or.cz, and OCMock from http://www.mulle-kybernetik.com/software/OCMock/

Xcode 3.1 includes SenTestingKit, so you don't need to download it separately.

To install Xcode, mount the disk image and follow the instructions.

To install git, +++ Write this when I get my MacBook back from Apple and I can do an install on a clean system.+++

To install OCMock, mount the disk image, and move a copy of OCMock.framework to /Developer/Library/Frameworks.

Figure out where to put the git public repository

In this tutorial, I'm going to configure two separate git repositories. The first is a public repository. Other people working on the project can access this. Or, if you have occasion to work on the project from two different computers, you can access it from both. Also, you can have your daily backup program back this up, as it won't include the large temporary and executable files that Xcode writes.

If you don't really need this because you're working alone on a computer that has only a single hard drive, well, do it anyway. It's good practice for when you will need it. (And, I'm too lazy to write two sets of instructions.)

Pick or make a directory on some network drive that everyone has the appropriate read and write permissions on. Or pick or make a directory that your daily back up program backs up.

(Note: Git does support web-based access, but I'm not going to explain that for this simple tutorial.)

I'm going to use "/Volumes/Public/Projects" as the directory for the public repository in this tutorial. Substitute your own directory name every time I use that one.

In a terminal window, type the following:

cd /Volumes/Public

mkdir Projects

Use Xcode to create a new project in the public directory

This is the only time you'll use Xcode to access this directory. After this, all access to this repository will be through git commands. Xcode will access your working directory. (Which is not the same as your private repository.)

Start Xcode.

File → New Project...

Select Application in the left pane. Select Cocoa Application in the right pane.

Click "Choose..."

Navigate to "/Volumes/Public/Projects", creating it if necessary.

Type "MyProject" in the "Save As:" field.

Click "Save"

Xcode → Quit Xcode

Use git to initialize the public repository

Now that Xcode has created the project directory, you need to create a git repository in it.

Execute the following commands in a Terminal window to create the public repository:

cd /Volumes/Public/Projects/

cd MyProject

git init

Next you need to store the project definition into the git repository.

git add English.lproj/ Info.plist MyProject* main.m

git commit

At this point, git will open the vim editor on a temporary file. The cursor will be on the first line, which is blank. The bottom part of the file shows the status of the various files that git sees, putting them in three different sections. First, in a section labeled "Changes to be committed", are the files that the commit will add to the repository. Second, in "Changed but not updated", are files that you've told git that you're interested in tracking and have changed, but you didn't put into the cache. Third, in "Untracked files", are files that you have not told git you are interested in tracking. If there are no files to display in a particular section, git will not display that section.

You should see something like this:


# Please enter the commit message for your changes. Lines starting

# with '#' will be ignored, and an empty message aborts the commit.

#

# Committer: Pat McGee <jpmcgee@MusicBox.local>

#

# On branch master

#

# Initial commit

#

# Changes to be committed:

# (use "git rm --cached <file>..." to unstage)

#

# new file: English.lproj/InfoPlist.strings

# new file: English.lproj/MainMenu.xib

# new file: Info.plist

# new file: MyProject.xcodeproj/TemplateIcon.icns

# new file: MyProject.xcodeproj/jpmcgee.mode1v3

# new file: MyProject.xcodeproj/jpmcgee.pbxuser

# new file: MyProject.xcodeproj/project.pbxproj

# new file: MyProject_Prefix.pch

# new file: main.m

#

According to this status, git is about to commit all the files listed as being new files. More importantly, there is not a section headed "Changed but not updated" or one headed "Untracked files". If there had been, this would mean that you mistyped some file name in the 'git add' command above.

In the case that git tells you something you don't expect, abort the 'git commit' command and fix things before trying it again. To abort the command, don't add anything to the file and exit vim. Type 'ZZ' or ':q" without inserting anything or type ":q!" if you already inserted something.

After you've verified that git will commit all the files yo want it to and only those files, insert some comment into the top line of the file and close the file. Make the comment reasonably informative, probably something like "Initial Xcode project definition".)

(Remember, vim is a mode-based editor. When it starts, you're in edit mode. In order to add something, you must change to insert mode, usually by typing 'i'. When you've changed to insert mode, the status line at the bottom of the terminal window will change to read "--- INSERT ---". If it says anything else, you're in edit mode. When you've finished adding, hit the ESC key to get back into edit mode. (Did the "INSERT" on the status line go away?) Then type 'ZZ' to close the file and exit vim.)

After you exit the editor, git will display a bunch of status messages. As long as they start with "Created commit...", you've done things correctly.

Use git to create your working repository and put the project files in it

Now you'll need to define a place for your private repository. This will be the directory in which Xcode will do its work. I'm going to use a subdirectory named "work" in my home directory. Of course, Xcode demands a subdirectory named "MyProject" inside "work".

Using the git 'clone' command will create that subdirectory, create a local repository, associate it with the public repository, and copy all the files from the public repository into both your private repository and the working directory.

cd ~/work

git clone /Volumes/Public/Projects/MyProject

Open the working directory in Xcode

Start Xcode

File → Open...

Navigate to ~/work/MyProject

Select MyProject.xcodeproj

Click Open

OK, we've now got an empty project in Xcode. That project is located in git's working directory. That directory is linked to the cache, which is linked to the private repository, which in turn is linked to the public repository.

Now we can start programming. Finally.

My sample application

I'm going to define a very simple application. When started, it will open a single window. The window will contain a button and and a label. When someone clicks the button, the program will display "42" in the label.

That's it.

But, in doing this, I'm going to show you how to build some interesting infrastructure that will make it much easier to expand the program to do much more. And, I'm going to try to do it in a very Test-Driven Development way, using unit tests and mock objects. Along the way, I'm going to show how to use the debugger to stop the program in the middle and examine variable values.

Add an AppDelegate class

I want to define a new class named "AppDelegate". I'll use this class to avoid dealing with lots of hard issues, like telling the Application object where to write data and how to respond to buttons and other stuff that should be left under the covers. Or maybe swept under the carpet.

Create a new class. Name it AppDelegate. Add an outlet for a text field, and an action that writes "42" to the outlet when the action is called. Save the files and build.

In the Groups & Files pane on the left of the Xcode window, right-click on Classes. Select Add → New File.... Select Cocoa in the left pane and Objective-C class in the right pane. Click Next.

Name it "AppDelegate" and click Finish.

In the Groups & Files pane, make sure the disclosure arrow for Classes points down to expose AppDelegate.h and .m. Click on AppDelegate.h. In the edit pane on the bottom right, add the following right after "@interface"

IBOutlet NSTextField *mylabel;

Add the following right after the "}":

- (IBAction) clickMe: (id) sender;

Click on AppDelegate.m. Add the following routine to the implementation section:

- (void) clickMe: (id) sender

{

}

Save the files and click Build.

Since this is the first time Xcode has done a build, it will take a few seconds longer than it will later. Xcode is saving a lot of intermediate results so it won't have to do them again.

Xcode should report "Succeeded" in the lower right corner of the MyProject pane and "Build Succeeded" in the bottom left.

If it doesn't, go back and check your typing. I'll wait.

Commit your changes to the repositories

Git helps you keep track of changes in your files. You've changed some files. It's time to tell git to put those changes into both repositories. Use 'git add' to add the changes to the cache, then 'git commit' to copy them from the cache to your local repository, then 'git push' to copy them to the public repository.

Remember to repeat these steps every time you make a significant change. You get to decide what is a significant change. If you make too many changes before you commit, you'll have a hard time figuring out what commit comment to type in. Later on, when you need to isolate a particular change, you'll have a hard time picking that one change out of the mass of them that you committed. OTOH, if you commit after every single edit, the important changes will get buried in a mass of detail.

The right answer depends on your particular context, and you'll find out where it is only by experimenting and getting it wrong a bunch of times. IMHO, I believe that the more people you work with, the fewer changes you should make before doing a commit, and that the right answer is always to commit about twice as often as you think you need to.

That said, never ever ever commit something that causes the code in the public repository to not compile, and only very rarely commit something that doesn't work. If you want to experiment with something and want to keep track of your interim changes, use branches. This is a topic beyond the scope of what I can write here.

First put the changes into the cache:

cd ~/work/MyProject

git add AppDelegate.* MyProject.xcodeproj

(Make sure you do not add 'build/'.)

Next copy the changes from the cache into your private repository:

git commit

You should see five files listed under the "Changes to be committed" section, none in the "Changed but not updated" section, and only "build/" in the "Untracked files" section.

Add a descriptive commit message. Remember the 'i' to tell vim to change to insert mode, the ESC to change back to edit mode, and the ZZ to close the file and exit.

Copy the changed file to the public repository:

git push

Add the AppDelegate object to the interface

In Interface Builder, add an NSObject named AppDelegate. Reclass it as AppDelegate and connect it as the delegate to the File Owner. After you've done this and saved the .xib file, use git to save the changed .xib file.

In the Groups & Files pane, flip the disclosure arrow on Resources.

Double-click on MainMenu.xib to start Interface Builder

Find the Library pane and bring it to the front. (+++ In a new install of Xcode, where does this default to? Is it shown? Where? If it isn't shown, how do I get it to the front? Since IB remembers this between executions, I can't tell from my current state.)

In the search field at the bottom of the Library pane, type "object".

Select the blue cube, NSObject.

Drag it to the "MainMenu.xib (English)" window. (Not the "Window" window.)

In the MainMenu window, select the new object's name and rename it to "AppDelegate".

With AppDelegate selected, bring the "Object Identity" inspector to the front. (Tools → Identity Inspector)

In the Class Identity section, pull down the Class widget and select "AppDelegate". (If AppDelegate is not in the pulldown, you didn't build the code before you started IB. Go back now and do that.)

Back in the MainMenu window, control-drag a line from "File's Owner" to "AppDelegate".

In the popup that appears, select the outlet: "delegate".

Save the file (Clover-S)

Commit the changes (using a shorter procedure this time)

After saving the .xib file, use 'git commit -a' to commit the changes to both the cache and your local repository.

This command combines the actions of 'git add' and 'git commit' by first looking for modifications in each of the files that git is tracking. If there are any, it sets up to do both an add and a commit. This is just a convenience command; it doesn't do anything different than 'git add' followed by 'git commit'. It's just less to type, and git does the work of figuring out which files changed, instead of requiring you to remember them.

In a terminal window, do the following:

cd ~/work/MyProject

git commit -a

Verify that the "Changes to be committed" section includes the MainMenu.xib file. (It will probably also include some files inside the MyProject.xcodeproj/ directory. I don't yet know what those files are for, so I'm ignoring them.)

Add a commit message, and exit the editor. (You do still remember about insert and edit modes, don't you?)

Add the button and field to the window

In Interface Builder, add a button. Connect it to the action in AppDelegate. Add a text label, and connect it to the outlet.

Open Interface Builder again.

In the Library pane, enter "button" in the search box.

Bring the "Window" window to the front. (This is the window that your application will show when you run it.)

Drag a button (I used the top left one) to the Window window.

Connect the button to the clickMe: method in AppDelegate by control-dragging a line from the button in the Window window to the AppDelegate object in the MainMenu.xib window. When you see the dark box with the Received Actions, select "clickMe:"

In the Library pane, enter "label" in the search box. Grab a label (I used the top left one), and drag it to an appropriate spot in the Window window.

Connect the label to the AppDelegate label outlet by control-dragging a line from the AppDelegate object in the MainMenu window to the label in the Window window. When you see the dark box with the Outlets, select myLabel.

Save the file.

Commit changes again in git

I'll leave you on your own this time.

Add code to actually do some work

The first thing to do is to add a WorkerBee class to the project.

You do this as a way to implement one of my Golden Rules of GUI programming: GUI code moves data from the GUI to the rest of the code; it never generates or changes any data. The worker code does all generating and manipulating of data; it never does anything related to the GUI. There is precisely one point of contact between the two. In Cocoa, the part that provides this one point of contact is the AppDelegate class / object.

(OK, if you've got a really big project, you might have multiple classes do this, probably one for each big window. But, if you've got that big a project, you wouldn't be reading this tutorial.)

There are other ways to design GUI applications. IMNSHO, this is the best for almost all small and medium-sized applications. Even if you don't agree, please do it this way for now until you understand how this works. Then, you can do whatever you want once you understand the basics.

So, create a class named "WorkerBee", and add a method named "getAnswer". Write code so that the method returns a string containing "42".

Add an instance variable to AppDelegate to hold an instance of a WorkerBee object. Add an initialization routine to instantiate a WorkerBee and save it.

Then add code in the clickMe: method of AppDelegate to get an answer from WorkerBee and send it to the AppDelegate label outlet.

In Groups & Files, right-click on Classes. Do Add → New File...: Cocoa, Objective-C class, Next. Name it "WorkerBee".

In WorkerBee.h, after the variables section of the interface, add the declaration for getAnswer.

- (NSString *) getAnswer;

In WorkerBee.m, add the following method definition:

- (NSString *) getAnswer {

return @"42";

}

Save your edits and build the project. If you see any errors, go back and fix your typos.

Edit AppDelegate.h. Add the following line of code right after the import of Cocoa:

@class WorkerBee;

Add the following line of code inside the instance variable section, right before the declaration of the label:

WorkerBee *myWorkerBee;

Save that file and open AppDelegate.m. Add the following right after the import of AppDelegate.h:

#import "WorkerBee.h"

(Remember to use double quotes as delimiters. That tells Xcode to look in the project directory for that file. Using angle brackets tells it to look in the library, not the working directory.)

Add the following method definition:

- (id) init

{

[super init];

myWorkerBee = [[WorkerBee alloc] init];

return self;

}

This code is run whenever AppDelegate is instantiated. It initializes the superclass before instantiating a WorkerBee object and saving a reference to it for later use.

Now add the following line inside the clickMe: method definition:

[myLabel setStringValue: [myWorkerBee getAnswer]];

This tells WorkerBee to give me an answer, and then display it in the GUI as the value of the label.

Just to be different, don't save the files before you click Build and Go. You should see a window asking if you want to save files before building. Click "Save All". Xcode looks out for you.

When your new application comes up, click the button. If all goes well, you'll see the answer to life and the universe.

Commit again

You should be starting to see my pattern for when to commit changes. Basically, I do it every time I get something to work.

From now on, I'll skip telling you to commit.

(BTW, you did remember to do 'git add WorkerBee.h' before doing 'git commit -a', didn't you?)

Debug your application

Before starting your new application in the debugger, change the code a little bit to make it more interesting to debug. In WorkerBee, add a temporary variable to hold the string. Then return it in the next line. This gives you a place to set a breakpoint when the value is in a convenient place.

Set a breakpoint by clicking in the debug strip to the left of the code line in WorkerBee that returns the answer.

Start the program in the debugger. When your window comes up, click the button. Xcode will display the code with an arrow showing it has stopped at that line.

At this point, you can view the contents of variables. For some types of variables, you can edit them.

(+++ I haven't figured out how to edit string values yet. I could possibly point the object to another string, if I could find one. But changing the value is, so far, beyond me.)

In WorkerBee.m, change the code inside the getAnswer method to be:

NSString *tempvar = @"42";

return tempvar;

Locate the debug strip (+++What is this really called? I just made up that name.) To the immediate left of the code in the window is a vertical stripe that shows various shadings. If you hover the mouse inside it, various arrows show up. Using this stripe allows you to do interesting things with exposing and hiding various code sections. That's not what we want. The debug stripe is one more to the left, and by default doesn't have anything in it.

Click once in the debug stripe next to the return line. You should see a blue boxy arrow. This says that you've set a breakpoint at that line of code.

Click Build and Go.

When your application's window shows up, click the button.

Xcode will switch back to the Xcode window with the line at the breakpoint highlighted, and the blue arrow changed to have a red head.

Hover over the variable "tempvar". Xcode will bring up a tool tip containing the type, name, and value of the variable. The value shows up as a memory address, and as a string containing "42".

From here, you can do several things. You can hover over the disclosure arrow next to the type to see the superclasses and the instance variables for them.

If you hover over the blank area to the immediate left of the type, a couple of up and down arrows will appear. Click on them (left-click, not right-click) and you'll get a menu. You can do "Open in Window" to have the variable info stay on the screen, instead of disappearing when you hover elsewhere.

You could edit the value of the variable. But unless you can locate another string object whose address you could put here, it wouldn't do you much good.

Finally, right-click on the blue/red breakpoint arrow to get a menu of interesting actions. Select "Continue to Here" to tell the application to continue, so it will display the answer and then wait for more input.

Remember that the application is stopped when at the breakpoint. So, it cannot respond to things like "Window Front" and "Hide" and "Quit". You must continue the application before those commands will work.

Adding unit tests

Now for the next big thing: adding unit tests to the code.

In writing unit tests, I'm assuming that it's only interesting to write unit tests for WorkerBee. Since AppDelegate only connects the WorkerBee methods to GUI elements, there's not much that could go wrong that wouldn't be pretty obvious. Since it's somewhere between difficult and impossible to test those connections using test automation and the rewards are so small, I don't bother trying to do that.

At the moment, I'm not going to get into how to run these tests automatically whenever you build the application; that comes later. Patience, Grasshopper.

Add a new target of type Cocoa Unit Test Bundle; name it "RunWorkerBeeTests".

Add the SenTestingKit framework to the new Bundle.

Add a unit test class to the Bundle; name it "TestWorkerBee".

Add a test case method to check to see whether getAnswer really does return 42.

Add the WorkerBee.m file to the RunWorkerBeeTests target.

Build the RunWorkerBeeTests target, which will run all the unit tests.

Change the value returned by WorkerBee getAnswer to 41 and Build again to see what a unit test failure would look like.

In the Groups & Files pane, right-click on Targets. (Actually, right-clicking pretty much anywhere in this pane works, but I can remember it better if I do it over Targets.)

Add → New Target...

Cocoa: Unit Test Bundle → Next

Name it "RunTests"

Flip the disclosure arrow next to Frameworks down so you can see Linked Frameworks. Control-click Linked Frameworks → Add → Existing Frameworks. Navigate to /Developer/Library/Frameworks. (The default is probably /Library/Frameworks, which is not what you want.) Select SenTestingKit.framework. Add. In the window that shows up next, add it just to the RunTests target, not to MyProject. (Again, we'll change this later as part of the process of telling Xcode to run the tests on every build.)

Class → Add → New...: Cocoa: Objective-C test case class. Name it TestWorkerBee. Before closing the "New File" dialog, make sure you add it to the TestWorkerBee target, not the application target.

In TestWorkerBee.h, add the following right after the #import line:

@class WorkerBee;

Add an instance variable to keep a WorkerBee instance:

WorkerBee *myWorkerBee;

In TestWorkerBee.m, add the import line:

#import "WorkerBee.h"

Add the code to create and dispose of a WorkerBee in setUp and tearDown:

- (void) setUp

{

myWorkerBee = [[WorkerBee alloc] init];

}


- (void) tearDown

{

[myWorkerBee release];

}

Add a test case that checks to see if [WorkerBee getAnswer] really returns "42".

- (void) testWB1

{

STAssertEqualObjects ([myWorkerBee getAnswer], @"42", @"secret meaning of the universe");

}

Tell Xcode that the WorkerBee class should be part of the RunWorkerBeeTests target. Do this by clicking the disclosure arrows under the target until you see the contents of the "Compile Sources" folder. Drag and drop "WorkerBee.m" into this folder.

OK, now set the target (selector box in upper left corner) to the test, and Build. This should compile for a couple of seconds, briefly show the window for the application, and then report that the build succeeded. This actually ran the tests. Since they passed, Xcode didn't display any messages.

To check to make sure you're really running the tests, change the code in WorkerBee to return @"41" and Build again. This time you should see that the build failed with 2 errors. Click on the red circle with the X to see the Build Results window. The first error says, " '41' should be equal to '42' secret meaning of the universe". (The second error just says that some tests failed.)

Oh, yeah, change the worker code to return 42 again.

Aside on capitalization and CamelCase

After decades of working on system where case didn't matter, then systems where it did matter, but no one used CamelCase, and then systems where people used CamelCase all the time, I'm having trouble with how it works in Objective-C. First, I only use a capital letter in a CamelCase word in places where I would start a new word. To my mind, Up isn't a thing that you set. (According to Paul Sabourin, it _is_ a thing that you throw. But that's another song.) So, in proper CamelCase, it should be "setup", not "setUp". But, nobody asked me.

Anyway, in Objective-C, only sometimes will misspelling a name get flagged by the compiler or even the runtime. If you call something that doesn't exist, it will often call a nil object. Since sending any message to a nil object returns nil without throwing an error, it's often not at all obvious why your code doesn't work.

I've been trapped this way several times, sometimes wasting an hour or two trying to figure out what went wrong.

Be warned, and be paranoid.

Debugging a unit test

Sometimes a unit test will fail and you won't be able to easily figure out why by inspecting the code. That's especially true for beginners in a new environment, like me. Sometimes I don't know whether I'm off by one in counting indices, or whether I've messed up how I store that index in a range. Debugger to the answer.

I don't know how to do this. Every time I try to run the debugger on some unit test code, Xcode runs my app instead. So, it doesn't stop at the breakpoints in the unit test code. If I put a breakpoint in WorkerBee and exercise that code in the app, it does stop.

+++ How can I tell Xcode that when I set the target to RunWorkerBeeTests and click "Build and Go" that I want it to run the unit tests, not the application?

+++ How can I tell Xcode that I want the debugger to stop somewhere in the unit test code?

Running unit tests when Xcode compiles the application

Instead of having to tell Xcode to run the unit tests separately from running the application, it would be really nice to have Xcode run the unit tests as part of the app build process. So, we do that next.

(+++Chris Hanson has some instructions for this. They are on his blog: http://chanson.livejournal.com/120263.html. I was unable to make them work. I don't know where I went wrong.)

Debugging a unit test after adding it to the application build process

(+++ I hope that solving the previous two problems will also solve this one.)

Defining and using mock objects

When writing a unit test, it's really nice if the code being tested does the same thing each time you call it, depending only on the current and past inputs. This can be hard when the code calls some other routine to do part of the work. For instance, what if the code you are testing checks the clock and does one thing between 8am and 5pm, and does something else the rest of the time? It's really inconvenient to have to run your test programs at night to check one function and during the day to test another.

So, Tim Mackinnon invented mock objects. (http://www.macta.f2s.com/Thoughts/Papers/mockobjects.pdf) Later, some people (whose name I can't figure out from their web page: http://www.mulle-kybernetik.com) at Mulle Kybernetik published OCMock.

Here's one way to create a real object that gets the current system time, and to create a mock object that gets a time determined by the test code.

(Note: adding functionality to handle different time zones and the offsets for Daylight Saving and Daylight Wasting Time are left as exercises for the student.)

Using OCMock to make mock objects

+++ I wrote some code for this, but it doesn't work. I think I've got a pretty simple problem, or at least one that would look simple if I could run the debugger on it. So, again, I'm blocked until I get the above two issues resolved.

FirstCocoaApp.rtf 13 2008-09-02 21:26:22