![]() |
Articles Feed |
Categories
Archives
- August 2010 (1)
- July 2010 (5)
- June 2010 (4)
- April 2010 (3)
- March 2010 (2)
- February 2010 (2)
- January 2010 (1)
- December 2009 (1)
- October 2009 (2)
- September 2009 (2)
- August 2009 (1)
- July 2009 (5)
- June 2009 (2)
- May 2009 (2)
- April 2009 (8)
- March 2009 (7)
- January 2009 (2)
- December 2008 (3)
- November 2008 (5)
- October 2008 (4)
- September 2008 (6)
- August 2008 (4)
- July 2008 (5)
- June 2008 (5)
- May 2008 (4)
- April 2008 (2)
- February 2008 (4)
- January 2008 (2)
- December 2007 (2)
- November 2007 (2)
- October 2007 (2)
- September 2007 (1)
- August 2007 (3)
- July 2007 (1)
- June 2007 (4)
- May 2007 (7)
- April 2007 (2)
- February 2007 (3)
- January 2007 (3)
- November 2006 (3)
- October 2006 (3)
- September 2006 (17)
- November 2004 (1)
TDD and iPhone - NSTimer
by: eric | May 11th, 2009 | 3 comments »
TDD on the iPhone is a challenging experience, especially when you’ve been spoiled by Ruby like I have been, but it can be done. I have a tutorial in the works for getting started, but in the meantime I’ll be writing bits and pieces about my experience. The first was here and this is the second. Test cases are written using the Google Toolbox for Mac. This particular example is about the NSTimer.
It keeps going and going
The app to your right is a simple app for experimentation purposes that runs Conway’s Game of Life. It has two buttons: advance and start. Advance moves one generation ahead each time you press it, whereas Start keeps advancing generations until you touch Stop. To implement that we use an NSTimer object which is programmed to call advance on the game object. The design is simple, shown here:
The GameRunner creates an NSTimer, the NSTimer sends the message to Game. Great but how do we test it? The less I know about something the more I tend to test it, which means I tend to write smaller and smaller tests. Let’s look at my first few tests of the GameRunner object:
- -(void) testSetsAndGetsTimeInterval {
- runner.interval = 0.30;
- STAssertEquals(0.30, runner.interval, nil);
- }
- -(void) testCreatesNSTimerObject {
- [runner start];
- STAssertNotNil(runner.timer, @"The timer was not created");
- STAssertTrue([runner.timer isValid], nil);
- }
- -(void) testNSTimerFields {
- runner.interval = (NSTimeInterval) 0.26;
- [runner start];
- STAssertEquals([runner.timer timeInterval], 0.26, nil);
- }
We’ve got some basic tests of creation. At this point I was stumped. I couldn’t check the other parameters of the timer, because they weren’t public variables, so at this point the code that actually created the timer looked like this:
- -(void) start {
- timer = [[NSTimer timerWithTimeInterval: interval
- target:nil
- selector:nil
- userInfo:nil
- repeats:true] retain];
- }
That doesn’t really help me does it? I have a timer with an interval, but it won’t call anything when it’s triggered. This is about the time I got to pair with Jake Scruggs during the Craftsman Swap, which he’s written about here. It went badly, somewhat embarrassingly for me since I’m supposed to be the expert, however we learn from failure. Let’s look at some of the steps Jake and I took that didn’t quite get us there.
The first pass on the test was simple. We needed a mock game, and it needed to have its advanceGeneration method called (Ed. note: I realize they aren’t called methods in Obj-C, to which I say: bite me.) each turn. Let’s try the brute force approach:
- -(void) testTimerCallsGameAdvance {
- MockGame *mockGame = [[MockGame alloc] init];
- runner.game = mockGame;
- [runner start];
- sleep(.30);
- STAssertTrue([mockGame advanceGenerationCalled], nil);
- }
I’m writing the original tests from memory, so I make the occasional error here, but it doesn’t matter since we got this wrong. This test here seemed the most logical. We ran the test and it failed, yea! Then we updated the code:
- -(void) start {
- timer = [[NSTimer timerWithTimeInterval: interval
- target:game
- selector:@selector(advanceGeneration)
- userInfo:nil
- repeats:true] retain];
- }
Experienced Cocoa developer’s have already discovered our error, please don’t ruin it for the rest of the readers. So after changing the code we run the test and…we fail. So from here Jake and I figured it out. Sleep won’t work because it stops the entire application, including the run loop that calls our timer, so we’ll just have to get the info out of the timer another way. We’re interested in testing that we’ve setup the timer correctly, and can trust that the timer just works. After a loooong time browsing documentation we figured it out. We could call this:
- -(void) testTimerCallsGameAdvance {
- MockGame *mockGame = [[MockGame alloc] init];
- runner.game = mockGame;
- [runner start];
- [runner.timer fire];
- STAssertTrue([mockGame advanceGenerationCalled], nil);
- }
This will fire the timer and validate that it is setup correctly. So we did this, our test passed, we ran the app and it….failed. We knew the timer was executing because our test was passing, but when we ran the real app it just didn’t do it. Stumped and frustrated we stopped for the day. Then time passed…..
Yesterday I had a candidate in for an apprenticeship position, and we attempted to solve the same problem. We re-evaluated the test and decided calling fire directly was cheating. We looked into a half-dozen other ways to test this and learned about the NSRunLoop. Too much coding in Windows had rotted my brain, the run loop in Objective-C is not the same as the one in Windows. Specifically the current run loop is accessible as an object. We wrote a new test:
- -(void) testTimerCallsGameAdvance {
- MockGame *mockGame = [[MockGame alloc] init];
- runner.game = mockGame;
- [runner start];
- [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow: 0.30]];
- STAssertTrue([mockGame advanceGenerationCalled], nil);
- }
It’s not perfect - the 0.30 relies on the hard coded default interval of 0.25, and that is too long, but it failed! In the process of learning about NSRunLoop we also realized I used the wrong method. The working code is this:
- -(void) start {
- timer = [[NSTimer scheduledTimerWithTimeInterval: interval
- target:game
- selector:@selector(advanceGeneration)
- userInfo:nil
- repeats:true] retain];
- }
See the word “scheduled” in front of Timer? That actually adds the timer to the default run loop. Now we ran our spec, ran our actual code, and it works! The moral of the story? Well if you’re persistent enough you can get your code under test, and if your test is correct then your code will work.
Apprentice Architecture
by: paul | May 6th, 2009 | 1 comments »
Good design sense is a skill that comes only from many years of coding experience. Sometimes it can be very difficult to make those design decisions when you are an apprentice or otherwise don’t have much experience.
When I was an apprentice, oftentimes I would get a problem from Micah (my mentor) and would solve it the simplest way possible. Then I would walk up to him with my head held high and say I was done. He would almost always point out some way the architecture was not easy to extend or that he didn’t even know java had goto statements.
As an apprentice, the most important thing is to get everything to work correctly; only then should you concentrate on making sure it is good code. Writing great code with great designs is not a skill that will appear overnight. It took years of honing my design sense to be able to see designs right away, and I have only scratched the surface of my mentor’s abilities.
When I was an apprentice, I lacked the experience and skill to make difficult architectural choices. Let alone the ability to get it right on the first pass. I instead stayed close to the code and continued to move forward writing working code. This led to many design mistakes, and I had to throw out plenty of code or refactor to a better design. This is an exercise in taking a step backwards to move forwards. I learned the value of working code as well as the value of refactoring to better designs. Content before form is an important lesson I learned as an apprentice. It laid the foundation for the later lesson that good form is essential for high quality code.
As an apprentice, ask your mentor when you have completed a task if they think there is a better design. Then refactor that design and compare the two. Once again, repetition and practice will pay off as these exercises help you develop a matured design sense. You will learn from having to solve the problem without being told the design first, and then later having to re-implement a better design. This practice will internalize your design sense and eventually you will start to see the better designs the first time through.
The big problem with trying to get the design right the first time is it leads to coders block. Staring at the blank screen, trying to figure out the best way to do something. Even if you are writing the wrong code, it is important to be moving in some direction. When I see the code, oftentimes the design I get will form around the reading of the code rather than trying to think in the abstract about the whole problem at once. For me the problem and the design for the solution would be too big of a mental model to fit in my head all at once. Don’t worry, as you get more experience, your mental context will expand too. Your ability to sustain concentration will build as well as you practice and become more experienced. With experience, you won’t have to think about as many of the small problems anymore.
Architecture is like most craftsman practices in the sense that there are breakthroughs but there is always more to learn. I read a blog recently where Uncle Bob Martin talks about thinking through the architecture of a problem for hours or days before he will start. When I was an apprentice, I would not have had enough experience or a large enough mental model to think about the architectural impact of a solution. Even as a journeyman, I still only have a big enough mental model to think about an aspect of a problem, write the code, then reevaluate where the solution stands in the context of the system.
For an apprentice, bad design should not be seen as the end of the world. As long as you are learning and progressing in the techniques of creating working code, you are moving in the right direction. Design sense is a long-term skill and you will have to be patient with it. Remember that practice makes perfect.
#

