Re: TDD and Xcode

Chris Hanson <[email protected]> Sat, 11 Oct 2008 19:16:20 -0700
Newsgroups gmane.comp.programming.test-driven-development,gmane.comp.programming.test-first-user-interfaces
Message-ID <[email protected]>
On Oct 11, 2008, at 7:41 AM, ae1fric wrote:

> I'm considering switching my development over to Python under=20=20
> Apple's Xcode development
> environment -- Cocoa and Core Data provide some significant=20=20
> advantages for the type of
> development I'm doing. The one downside so far is that I haven't=20=20
> been able to figure out how
> to do TDD under Xcode.
>
> Has anyone successfully developed with TDD under Xcode?

Yes, there are actually quite a few people who do extensive TDD on Mac=20=20
OS X using Cocoa and Xcode =97 it turns out that it's actually a great=20=20
platform for doing so, thanks to its objects-all-the-way-down (for the=20=20
most part) design.

Disclosure: I work on the Xcode IDE at Apple, but I don't speak for=20=20
Apple or the Xcode team here or anywhere else online.  (Unless you=20=20
happen to be watching me in a WWDC video...)

> I think the reason I'm struggling to see how to do TDD under Xcode=20=20
> is that the development
> environment does so much automagically: you define your data model,=20=20
> draw your user
> interface, define bindings between the data model and the UI and=20=20
> suddenly you have a
> working application, without writing a line of code.
>
> Any suggestions for using TDD in this kind of environment would be a=20=20
> great help.

You can actually still do TDD by writing unit tests that specify how=20=20
your data model should be set up, and how the connections between=20=20
objects in your nib files -- whether those connections are target-=20
action, outlet or binding connections -- are set up.

Virtually everything that Xcode, Interface Builder and Cocoa let you=20=20
do graphically can also be done in code.  None of them *generate* code=20=20
to do so, but the objects that are created are the same either way, so=20=20
you can just introspect them.

For example, lets say you want to create a View Controller that has a=20=20
Name field for an employee, which is managed by an object controller.  =
=20
You can then write a test that you want the NSTextField's value=20=20
binding to be connected to the "selection" controller key path and=20=20
"name" model key path of an Employee Controller.

Here's an Objective-C test method that does just that written against=20=20
Sen:te's OCUnit framework that's included with Xcode.  Using PyObjC=20=20
and a Python testing framework should be the similar except for the=20=20
obvious differences in syntax and setting up your own type of test rig.

// This assumes viewController has nameField and employeeController=20=20
properties for
// the appropriate IBOutlets, and is configured in -setUp
- (void)testNameFieldBoundToName {
     NSDictionary *info =3D [viewController.nameField=20=20
infoForBinding:NSValueBinding];
     STAssertNotNil(info,
         @"The name field's value should be bound.");

     NSObjectController *observedController =3D [info=20=20
objectForKey:NSObservedObjectKey];
     STAssertEquals(observedController,=20=20
viewController.employeeController,
         @"The name field's value should be bound to the employee=20=20
controller.");

     NSString *observedKeyPath =3D [info=20=20
objectForKey:NSObservedKeyPathKey];
     STAssertEqualObjects(observedKeyPath, @"selection.name",
         @"The name field's value should be bound to the 'selection'=20=20
controller key "
         @"and 'name' model key of the employee controller.");
}

I've usually created my own assertion macros to encapsulate this kind=20=20
of test, of course; then the test winds up looking like this:

- (void)testNameFieldBoundToNameWithBindingAssertionMacro {
     MyAssertObjectIsBoundThroughKeyPath(viewController.nameField,=20=20
NSValueBinding,
         viewController.employeeController, @"selection", @"name",
         @"The employee name field should be bound to the employee=20=20
controller.");
}

Note that this isn't specifying the layout of the text field in my nib/=20
xib file, its font, or anything like that. You can specify those in=20=20
tests too, if you want to, but you don't have to.  Whether you do so=20=20
or not depends on the level of detail you need for your UI and the=20=20
level of churn that it's likely to go through.

For example, if you're changing the look and feel or layout of your=20=20
UI, you might not want to specify any of that, but the above test=20=20
would probably be very useful in ensuring you don't forget to bind up=20=20
a text field you've just replaced as part of iterating on your UI=20=20
design.

On the other hand, I wrote some code a while back to create a "glass=20=20
bar" like at the bottom of the message list in Mail.app on Leopard.  I=20=20
actually did write tests to specify the layout of that interface down=20=20
to the point, despite creating it in Interface Builder, because it=20=20
needed a very specific layout and I didn't want to accidentally change=20=20
it as I adjusted other things in the xib.

One other thing this doesn't do is "push" events through controls to=20=20
test that your controller-level code reacts to them.  In general you=20=20
don't need to write such tests except for your own custom controls.=20=20=20
Otherwise, you can trust that controls will work correctly as long as=20=20
they're connected correctly, and just verify that those connections=20=20
are set up right in your tests.

The same philosophy can be followed when writing tests that describe=20=20
your data model.  An NSManagedObjectModel can be loaded=20=20
programmatically and you can just ask for its entities and their=20=20
properties, and walk across the entire thing looking at their values=20=20
in code.  That makes it very easy to TDD because you can just create=20=20
an infrastructure of tests that say "this model should have an entity=20=20
named Employee" and "the Employee entity should have a required name=20=20
attribute" and "the Employee entity should have a to-one 'department'=20=20
relationship to the Department entity, with an 'employees' inverse=20=20
relationship."

Here are some of the blog posts I've written on the topic of writing=20=20
tests for Cocoa over the years:

	Xcode: Unit testing Cocoa frameworks
	http://chanson.livejournal.com/119303.html
=09
	Xcode: Debugging Cocoa framework unit tests
	http://chanson.livejournal.com/119578.html
=09
	Xcode: Unit testing Cocoa applications
	http://chanson.livejournal.com/120263.html
=09
	Xcode: Debugging Cocoa application unit tests
	http://chanson.livejournal.com/120740.html
=09
	Trust, but verify.
	http://chanson.livejournal.com/118380.html
=09
	Unit testing Cocoa user interfaces: Target-Action
	http://chanson.livejournal.com/148204.html
=09
	Unit testing Cocoa user interfaces: Cocoa Bindings
	http://chanson.livejournal.com/172390.html
=09
	Unit testing and Core Data
	http://chanson.livejournal.com/115621.html
=09
	My blog's "unit testing" tag
	http://chanson.livejournal.com/tag/unit+testing

Erik Doernenburg, the creator of the useful OCMock mock-objects=20=20
framework, has also written a bit about how to use OCMock to test=20=20
Cocoa controllers.

	Testing Cocoa Controllers with OCMock
	http://erik.doernenburg.com/2008/07/testing-cocoa-controllers-with-ocmock/

His approach is very appropriate for testing that your own controller=20=20
objects react to changes in your UI correctly, again without having to=20=20
"push events" through controls.

Let me know if there's anything else you'd like me to write about!

And if there are any ways you'd like to see Xcode, Interface Builder,=20=20
or Cocoa improved, or if you run into any bugs or other problems,=20=20
please file an enhancement request or bug report at http://bugreport.apple.=
com/=20
  =97 bugs and feature/enhancement requests go into Apple engineering's =
=20
Radar system and are evaluated by engineering and management.  Even=20=20
"duplicate" bug reports are extremely useful because they can wind up=20=20
having the critical nugget of information that is needed to fix a bug=20=20
(by making it reproducible or revealing a common thread), or can be=20=20
used to gauge demand for specific kinds of enhancement.

   -- Chris



[Non-text portions of this message have been removed]


------------------------------------

Yahoo! Groups Links

<*> To visit your group on the web, go to:
    http://groups.yahoo.com/group/testdrivendevelopment/

<*> Your email settings:
    Individual Email | Traditional

<*> To change settings online go to:
    http://groups.yahoo.com/group/testdrivendevelopment/join
    (Yahoo! ID required)

<*> To change settings via email:
    mailto:[email protected]=20
    mailto:[email protected]

<*> To unsubscribe from this group, send an email to:
    [email protected]

<*> Your use of Yahoo! Groups is subject to:
    http://docs.yahoo.com/info/terms/