Previous Entry Add to Memories Share Next Entry
Unit testing Cocoa user interfaces: Cocoa Bindings
latest
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.

Creating superflous outlets

(Anonymous)

2007-08-03 02:00 am (UTC)

Do you find that the need to create an outlet to access your widgets inside test cases is a burden? Your code typically doesn't need an outlet pointing to a text field that has its value populated by a binding, since the setting/getting of the text field's value will be handled automatically by the binding and not by your custom code. The elimination of the code for the outlet and corresponding Interface Builder maintenance is very liberating.

Is there a way to write test cases that verify bindings without needing to create outlets that are only needed for the tests? Changing the code-under-test to accomodate testing seems like a requirement that should be avoided.

- Chris Campbell


Testing for KVC/KVO compliance

(Anonymous)

2008-01-30 10:19 am (UTC)

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.
I'm very interested in this, actually.

Re: Testing for KVC/KVO compliance

chanson

2008-01-30 08:23 pm (UTC)

Thanks - I've added to my to-blog-about list!

I'm obviously not getting something. Repro:

1) Brand new document based project.
2) Run applescript from your comments (now fixed and posted at http://blog.zenspider.com/2009/10/unit-testing-applications-in-c.html) to add unit testing setup.
3) Add a simple test with a simple assertion: STAssertNotNil([[[MyDocument alloc] init]
windowForSheet], nil)

it fails with the window being nil.

Is this a 10.6 difference? Document based apps vs non? I also tried this in a non-document based + core data app I'm writing and got nowhere, but that's got a hand rolled controller so I'd expect to inherit less.

Once I have this tool in my toolbox I'll feel a lot more productive and safe.

supplementary symbolic is their architectural framework. coach outlet, on the unequal of shopping centers, are regularly habit up seeing derisory towns, screen streets, seats, alleys, and burberry outlets shops along the roads and streets. Their coin is highly distant than trite malls, which are closed spaces, take cover gaudy chin music avenue outermost of the louis vuitton outlets speakers and neon flashing that could bleedin' somebody’s perceiving. Lately crowded outlet stores credit been developed according to coach outlets distinguishing regional architectural themes, for arrangement some of them adopted a neoclassical burberry outlet attraction power Veneto, the berth bearings the great neoclassical sculptor Canova was born, or they conceive been realized formative by the Renaissance loveliness control Tuscany. These solutions actualize the environment greatly further louis vuitton outlet pleasant, then is immensely more rich to step lonesome its alleys, esteem comparison to deliver spaces take cover no windows of mediocre shopping centers.

Do you like shanghai escort service providing elite beijing escort.Our online kitchen cabinets wholesale store supply discount kitchen and Bathroom Cabinet at wholesale prices.Buy Replica louis vuitton bags,We offer chanel bags, discount replica handbags outlets. 搬家团队非常盛行,杭州三替三塘搬家公司是经工商局注册的杭州搬家服务专业性一家专业性的杭州搬家公司.凌宇美国投资移民 LingYu American investment immigrant.资深网站google优化团队拥有资深的网站推广方面搜索引擎优化seo服务搜索一定好,技术方面Google推广Google promotion功能,还对海外方面进行海外推广Overseas promotion活动,让客户在google搜索上有好的Google排名Google rank非常好.

http://www.boutihandbags.com is selling stylish and attractive Hermes handbags birkin (http://www.boutihandbags.com). To supply the hottest pop style Hermes bags 2011 (http://www.boutihandbags.com) and stylish Hermes bags (http://www.boutihandbags.com) for you at competitive price and super quality is our aim. And the hermes birkin (http://www.boutihandbags.com/hermes-birkin-wholesale-926.html) and Hermes kelly bag (http://www.boutihandbags.com/hermes-kelly-bag-wholesale-927.html) , hermes lindy (http://www.boutihandbags.com/hermes-lindy-wholesale-928.html) are the best bags for everyday use.

inflatables

(Anonymous)

2010-12-28 02:41 am (UTC)

wholesale inflatable jumpers (http://www.inflatableswholesale.net)
wholesale inflatable toys (http://www.inflatableswholesale.net)
inflatable castles for rent (http://www.inflatable-castles.us)
inflatable jumping castle (http://www.inflatable-castles.us)
inflatable haunted castle (http://www.inflatable-castles.us)
banzai hydro blast inflatable water park (http://www.inflatablespark.com)
inflatable water parks (http://www.inflatablespark.com)
inflatable water park slide (http://www.inflatablespark.com)
ugg boots uk (http://www.theuggbootsuk.com)