Unit testing Cocoa user interfaces: Cocoa Bindings
[info]chanson
About a year ago, I wrote about unit testing target-action connections for Cocoa user interfaces. That covers the traditional mechanism by which user interfaces have typically been constructed in Cocoa since the NeXTstep days. However, with the release of Mac OS X 10.3 Panther we've had a newer interface technology available — Cocoa bindings — which has presented some interesting application design and testing challenges.

Among other hurdles, to properly use Cocoa bindings in your own applications, you need to ensure that the code you write properly supports key-value coding and key-value observing. However, since the release of Mac OS X 10.4 Tiger, the necessary APIs have been available to easily do test-driven development of your application's use of Cocoa bindings, following a trust, but verify approach. (It's also been quite easy from the start to test your support for key-value coding and key-value observing, to ensure that your code meets the necessary prerequisites for supporting bindings. I can write more on this topic in another post if anyone is interested.)

The key to writing unit tests for Cocoa bindings is the -infoForBinding: method in AppKit's NSKeyValueBindingCreation informal protocol. Using this simple method, you can interrogate any object that has a binding for all of the information about that binding! It simply returns a dictionary with three keys:
  1. NSObservedObjectKey, which is the object that the binding is bound to;
  2. NSObservedKeyPathKey, which is the key path that is bound — in Interface Builder terms, this is the controller key path combined with the model key path, with a dot in between them; and
  3. NSOptionsKey, which is a dictionary of additional binding options unique to the binding. These are all of those additional checkboxes and pop-ups in the Interface Builder bindings inspector for setting things like a value transformer.
By specifying what this dictionary should contain for a particular binding, you can describe the binding itself and thus start doing test-driven development of your Cocoa bindings-based user interface. Note that all of the system-supported binding names — as well as the binding option names — are specified in <AppKit/NSKeyValueBinding.h> and are documented, too!

Let's take a simple example, like the one in last year's target-action example, of a window controller whose window has a static text field in it. The field should have its value bound to the name of a person through an object controller for that person. Assume that I've already created the test case and set up some internal methods on my window controller to refer to the contents of the window via outlets, and to load the window (without displaying it) in -setUp just like in the target-action example.

First, to see that my text field has a value binding, I might write something like this:
- (void)testPersonNameFieldHasValueBinding {
    NSTextField *personNameField = [_windowController personNameField];

    NSDictionary *valueBindingInfo = [personNameField infoForBinding:NSValueBinding];
    STAssertNotNil(valueBindingInfo,
        @"The person name field's value should be bound.");
}
Of course, this tells us nothing about how the binding should be configured, so it needs some fleshing out...

Let's check the object and key path for the binding.
- (void)testPersonNameFieldHasValueBinding {
    NSTextField *personNameField = [_windowController personNameField];

    NSDictionary *valueBindingInfo = [personNameField infoForBinding:NSValueBinding];
    STAssertNotNil(valueBindingInfo,
        @"The person name field's value should be bound.");

    NSObjectController *personController = [_windowController personController];
    STAssertEquals([valueBindingInfo objectForKey:NSObservedObjectKey], personController,
        @"The person name field should be bound to the person controller.");

    STAssertEqualObjects([valueBindingInfo objectForKey:NSObservedKeyPathKey], @"name",
        @"The person name field's value should be bound to the 'name' key.");
}
Not very exciting, and a little verbose, but it'll easily lead us through what needs to be set up in Interface Builder for this binding to work. If you want to cut down the verbosity, you can of course extract a method to do the basic checking...
- (BOOL)object:(id)object shouldHaveBinding:(NSString *)binding
            to:(id)boundObject throughKeyPath:(NSString *)keyPath
{
    NSDictionary *info = [object infoForBinding:binding];

    return ([info objectForKey:NSObservedObjectKey] == boundObject)
            && [[info objectForKey:NSObservedKeyPathKey] isEqualToString:keyPath];
}

- (void)testPersonNameFieldHasValueBinding {
    NSTextField *personNameField = [_windowController personNameField];
    NSObjectController *personController = [_windowController personController];
    
    STAssertTrue([self object:personNameField shouldHaveBinding:NSValue
                           to:personController throughKeyPath:@"name"],
    @"Bind person name field's value to the person controller's 'name' key path.");
}
If you're writing code that needs, say, a value transformer, it's a simple matter to extend this model to also check that the correct value transformer class name is specified for the NSValueTransformerNameBindingOption key in the binding options dictionary returned for NSOptionsKey.

You can even extract these kinds of checks into your own subclass of SenTestCase that you use as the basis for all of your application test cases. This will let you write very concise specifications for how your user interface should be wired to the rest of the code, that you can use to just walk through Interface Builder and connect things together — as well as use to ensure that you don't break it accidentally by making changes to other items in Interface Builder.

This is the real power of test-driven development when combined with Cocoa: Because you can trust that the framework will do the right thing as long as it's set up right, you simply need to write tests that specify how your application's interface should be set up. You don't need to figure out how to create events manually, push them through the run loop or through the window's -sendEvent: method, how to deal with showing or not showing the window during tests, or anything like that. Just ensure that your user interface is wired up correctly and Cocoa will take care of the rest.

Unit testing Cocoa user interfaces: Target-Action
[info]chanson
It's really great to see that a lot of people are adopting unit testing for their projects and dramatically improving their quality. Test-driven development and agile development methodologies built around it are really taking off. However, a lot of people still feel that their user interface is difficult to test through code, and either requires a capture-playback tool or requires a different design approach based heavily on interfaces/protocols to get right.

In last year's post Trust, but verify. I tried to dispel some of the mystery of testing your application's user interface when using the Cocoa frameworks. However, I've still had a lot of (entirely well-justified!) requests for examples of how to put it into practice. So here's a simple example of what I'd do to write a unit test for a button in a window that's supposed to perform some action.

First, when implementing my window, I'd follow the standard Cocoa pattern of having a custom NSWindowController subclass to manage my window. This window controller will have an outlet connected to each of the views in the window, and will also wind up with a private accessor method — used only within the class and any subclasses, and in testing — for getting the value of each of its outlets. This design flows naturally from the test which I would write to specify that the window should contain a button. First, here's the skeleton into which I'd put tests:
// TestMyWindow.h

#import <SenTestingKit/SenTestingKit.h>

@class MyWindowController;

@interface TestMyWindow : SenTestCase {
    MyWindowController *_windowController;
    NSWindow *_window;
}
@end

// TestMyWindow.m

#import "TestMyWindow.h"
#import "MyWindowController_Private.h"

@implementation TestMyWindow

- (void)setUp {
    // MyWindowController knows its nib name and
    // invokes -initWithWindowNibName: in -init
    _windowController = [[MyWindowController alloc] init];

    // Load the window, but don't show it.
    _window = [_windowController window];
}

- (void)tearDown {
    [_windowController release];
    _window = nil; // owned by _windowController
}

@end
That's the infrastructure into which I'd put my other test methods for this window. For example, I'll want to specify the nib name for the window controller and ensure that it actually knows its window:
- (void)testNibName {
    STAssertEqualObjects([_windowController windowNibName], @"MyWindow",
      @"The nib for this window should be MyWindow.nib");
}

- (void)testWindowLoading {
    STAssertNotNil(_window,
      @"The window should be connected to the window controller.");
}
Now let's check that I have a "Do Something" button in the window, and that it sends an action directly to the window controller.
- (void)testDoSomethingButton {
    // _doSomethingButton is a private method that returns the button
    // conected to the doSomethingButton outlet
    NSButton *doSomethingButton = [_windowController _doSomethingButton];
    
    STAssertNotNil(doSomethingButton,
      @"The window should have a 'Do something' button.");
    
    STAssertEqualObjects([doSomethingButton title], @"Do Something",
      @"The button should be titled accordingly.");

    STAssertEquals([doSomethingButton action], @selector(doSomething:),
      @"The button should send -doSomething: to its target.");

    STAssertEquals([doSomethingButton target], _windowController,
      @"The button should send its action to the window controller.");
}
You'll notice something I'm not doing in the above: I'm not simulating interaction with the interface. This is the core of the trust, but verify approach to unit testing of your user interface.

I can trust that as long as I verify everything is hooked up properly that Cocoa will cause the button to send its action message to its target — whether it's a specific object or, if the target is nil, the responder chain — whenever the button is clicked while it's enabled and not hidden. I don't need to simulate a user event, and I don't even need to display the interface while running the unit tests. All I need to do is inspect, through code, that everything is wired up correctly.

Note that I can do way more than the above in testing my interface design, too. For example, I can ensure that the control layout is correct according to what my interface designer has specified, by checking bounding rectangles for example. But testing only the functionality of my interface has significant advantages, too. For example, it doesn't matter if I wind up using a custom kind of button to achieve exactly the kind of look and feel or behavior I need. It doesn't matter if I wind up changing the layout in response to feedback. No matter what I do, I'll know that functionality won't accidentally break while I'm messing around in Interface Builder — even if I completely rip out my interface and replace it with a new one!

This approach can also be used for testing Cocoa bindings using the -infoForBinding: method that was introduced in Mac OS X 10.4 Tiger. I hope to write up a post soon on how to approach Cocoa bindings using these same techniques, but it should be fairly straightforward given the above and the above documentation.

Update: I've struck through the check of the button's title above, because you may or may not want to do that. For example, if you're primarily running your unit tests against your development localization, you may want to put it in. But if you want to run your unit tests against a localized build of your application, you'll probably want to avoid checking a localized title against an English string. A "have your cake and eat it too" strategy might be to keep a variable somewhere in your application that can be used to selectively disable checks of only localized strings.

Update July 7, 2007: I've finally written a post, Unit testing Cocoa user interfaces: Cocoa bindings, on how to write tests for Cocoa bindings. Now there's no excuse for not doing test-driven development of your Cocoa user interfaces!

Happy birthday, Mac OS X!
[info]chanson
Five years ago yesterday, Mac OS X was released. I managed to get a few people together at Gameworks in Schaumburg, IL to celebrate the release, which was a lot of fun.

Things have come a long, long way since that time. Congratulations to everyone who has helped to make Mac OS X a success — I'm proud to play my small part in it, as a third-party developer for most of my career, and lately in Development Technologies at Apple.

I can't wait to see what the next five years brings, especially as technologies like Cocoa bindings, Core Data, and Quartz Composer become more and more mainstream. The "force multipliers" available to Mac OS X developers enable truly amazing applications to be created.

The brick wall thanks you!
[info]chanson
Charles Miller, Weighing into the Static vs Dynamic Typing Debate, The Fishbowl:
One place where dynamic typing has truly 0wned me, however, has been the Cocoa framework for OS X. Cocoa have shown me that programming a GUI doesn't have to be an exercise in banging my head against a brick wall, it can actually be fun. A lot of the flexibility of Cocoa comes from the dynamic nature of Objective-C. If you've got a Mac and you haven't learned Cocoa yet, set aside a week to go through a tutorial or two. You won't be disappointed.
That's almost exactly what I thought when I first started learning OpenStep development on a used NeXTstation in 1997. OpenStep was to developing software what the Macintosh was to using it.

I haven't lost that feeling: OpenStep got even better when it became Yellow Box, and Yellow Box got even better when it became Cocoa. And Cocoa just keeps getting better with features like NSNetService in Mac OS X 10.2 and Cocoa Bindings in Mac OS X 10.3.

As for static versus dynamic typing: I think Objective-C is a great compromise. (What, you didn't see that one coming?) All of the actual messaging is done at runtime, dynamically; there is no static binding. However, you can — if you want — use types when writing your code to let the compiler help you. No, there are no templates for typed collections; this winds up not being a significant problem in practice, and it enables you to easily have heterogeneous collections when you need them.

Use id — the "any object" type in Objective-C, which supports multiple root classes — whenever you truly don't care about type, use a protocol (where Java got the idea for interfaces) when you only care about whether the receiver responds to a very restricted subset of messages, and use a full type when you actually care about the receiver being of that class or a subclass. And use unit testing for everything else. It works quite well.

Platform Futures
[info]chanson
On Windows, many developers seem to want to run as fast as possible away from Microsoft Visual C++ and embrace Microsoft's C# and .NET platform for new development. Most Windows developers that I've seen seem downright enthusiastic about these technologies. It's disconcerting; I'm not used to seeing Windows developers (or users) be enthusiastic about their platform.

On the Mac, many developers are trying to hold onto C++ and Carbon for as long as they can, even for new development. A new Mac developer on the Carbon list actually said he wished Apple had a C++ framework that used MFC-like "message maps" for Mac OS X-only Carbon development "to make it easier to build software fast!" (Paraphrased.) And Metrowerks is spending money & time building a next-generation C++ PowerPlant framework for Mac OS X-only Carbon development! And some developers keep on Apple's case to try and maintain feature parity between Carbon and Cocoa.

Fortunately, Apple isn't giving in to them as much as they might think. For instance, WebKit has a Carbon wrapper, but it's just a wrapper; WebView is really a Cocoa framework and if you want to extend it you're going to have to use Cocoa. The Cocoa Controller layer is only really possible to do with a rich dynamic runtime; it'll never make it to Carbon. You can only build screen savers using Cocoa and Objective-C. You can only build preference panes using Cocoa and Objective-C. Virtually all new applications coming out of Apple are built using Cocoa and Objective-C.

(Keynote, SoundTrack, LiveType, iCal, iPhoto, iSync, iChat AV, Safari... Final Cut and Logic don't count, since they ware originally developed for the traditional Mac OS and thus aren't new. Neither does Shake, since it was originally developed for Irix and X11 — though it wouldn't surprise me at all to see it rearchitected as a Cocoa application in the next couple of years.)

The future of development on Windows is C# and .NET. This has been clear since Microsoft first released .NET, and it's especially clear in light of the latest PDC and Longhorn.

The future of development on the Mac is Objective-C and Cocoa. This has been clear ever since Apple bought NeXT, and it's especially clear in light of the latest WWDC and Panther.

Deal with it.