My First TDD Kata – Objective C – Xcode 4.5 – OCUnit – ARC

Ok, so I’ve taken the entire day to work on a programming Kata using TDD.

I started with the same exercise that I was given during an interview for an industrial placement a few weeks ago http://codekata.pragprog.com/2007/01/code_kata_one_s.html just so that I had an idea of where to start.

This code has taken me all day but for good reason.
Firstly I’ve never used TDD before, nor do I have any idea of how to use OCUnit. I have quite limited experience in programming generally so this was all a bit of a learning curve for me.
Secondly, this paradigm is basically the opposite of the way in which I’ve learned to program. I find it very, very difficult to think this way.

I’m actually quite pleased with the result. As applications go it’s functional, seems robust and the design isn’t far off from what I would have designed otherwise. It will be interesting to come back to this Kata later and see what differences there are.

What did I learn?

TDD is a concept, I could hear the criticisms screaming out at me as I did it but once I got going it actually started to come really naturally.

Two problems.
From time to time a tricky change comes up. For example in my application I made the decision to change from scanning in Strings to scanning in item objects. Looking back now I can see how I maybe could have handled this differently and maybe made things a little bit easier on myself. But still there was a lot of time spend pondering this, while outside of TDD the change would have been a no-brainer, just change an argument here and a return type there an done. TDD meant that I wound up writing a whole new function for it to save failing the older tests. I’m hoping that some more practice in TDD will help me to handle this type of situation in the future.

There was a real sense of fragility when programming. When implementing the BOGOF feature I found myself tip-toeing around the code trying to change as little as possible as not to fail my older tests, again I hope that experience will take care of this but I’m not so sure.

The design comes from the refactoring stage really. Refactoring seems to be the time to do a little bit of ‘Crystal Balling’ a little bit and try to introduce some good code and design practices.

Oh and if you’re wondering about the weird way I’ve worked with NSNumber, well I can’t really explain myself, I just had a really hard time with it and the primitives. I may do my next kata in a different language.

My Code.
Tests

 -(void)testCheckOut {  
NSNumber *expected = [NSNumber numberWithDouble:0.0];
NSNumber *result = [scanner checkOut];
STAssertEquals([expected doubleValue], [result doubleValue], @"Expected %g, but returned %g", [expected doubleValue], [result doubleValue]);
}
-(void)testScanItem {
NSNumber *expected = [NSNumber numberWithDouble:0.60];
[scanner scanItem:(@"apple")];
NSNumber *result = [scanner checkOut];
STAssertEquals([expected doubleValue], [result doubleValue], @"Expected %g, but returned %g", [expected doubleValue], [result doubleValue]);
}
-(void)testScanningTwoItems {
NSNumber *expected = [NSNumber numberWithDouble:1.20];
[scanner scanItem:(@"apple")];
[scanner scanItem:(@"apple")];
NSNumber *result = [scanner checkOut];
STAssertEquals([expected doubleValue], [result doubleValue], @"Expected %g, but returned %g", [expected doubleValue], [result doubleValue]);
}
-(void)testScanningAnItemWithADifferentPrice{
double expected = 0.7;
[scanner scanItem:@"banana"];
double result = [[scanner checkOut] doubleValue];
STAssertEquals(expected, result, @"Expected %g, but returned %g", expected, result);
}
-(void)testScanningTwoItemsWithDifferentPrices {
double expected = 0.7+0.6;
[scanner scanItem:@"apple"];
[scanner scanItem:@"banana"];
double result = [[scanner checkOut] doubleValue];
STAssertEquals(expected, result, @"Expected %g, but returned %g", expected, result);
}
-(void)testScanningMultipleItemsObjectsOfVaryingPrice {
Item* apple = [[Item alloc] initWithDescription:@"apple" andPrice:[NSNumber numberWithDouble:0.6]];
Item* banana = [[Item alloc] initWithDescription:@"banana" andPrice:[NSNumber numberWithDouble:0.7]];
Item* orange = [[Item alloc] initWithDescription:@"orange" andPrice:[NSNumber numberWithDouble:0.5]];
double expected = 0.0;
[scanner scanItem:apple.description];
expected += [apple.price doubleValue];
[scanner scanItem:banana.description];
expected += [banana.price doubleValue];
[scanner scanItem:orange.description];
expected += [orange.price doubleValue];
[scanner scanItem:apple.description];
expected += [apple.price doubleValue];
double result = [[scanner checkOut] doubleValue];
STAssertEquals(expected, result, @"Expected %g, but returned %g", expected, result);
}
-(void)testScanningItemAsObject {
Item* apple = [[Item alloc] initWithDescription:@"apple" andPrice:[NSNumber numberWithDouble:0.6]];
double expected = 0.6;
[scanner scanObject:apple];
double result = [[scanner checkOut] doubleValue];
STAssertEquals(expected, result, @"Expected %g, but returned %g", expected, result);
}
-(void)testScanningMultipleObjects {
Item* apple = [[Item alloc] initWithDescription:@"apple" andPrice:[NSNumber numberWithDouble:0.6]];
Item* banana = [[Item alloc] initWithDescription:@"banana" andPrice:[NSNumber numberWithDouble:0.7]];
double expected = apple.price.doubleValue + banana.price.doubleValue;
[scanner scanObject:apple];
[scanner scanObject:banana];
double result = [[scanner checkOut] doubleValue];
STAssertEquals(expected, result, @"Expected %g, but returned %g", expected, result);
}
-(void)testBOGOFWithTwoOfTheSameItem {
Item* apple = [[Item alloc] initWithDescription:@"apple" andPrice:[NSNumber numberWithDouble:0.6]];
double expected = apple.price.doubleValue;
[scanner scanObject:apple];
[scanner scanObject:apple];
double result = [[scanner checkOut] doubleValue];
STAssertEquals(expected, result, @"Expected %g, but returned %g", expected, result);
}
-(void)testBOGOFWithMultipleVaryingItems {
Item* apple = [[Item alloc] initWithDescription:@"apple" andPrice:[NSNumber numberWithDouble:0.6]];
Item* banana = [[Item alloc] initWithDescription:@"banana" andPrice:[NSNumber numberWithDouble:0.7]];
double expected = 0.0;
[scanner scanObject:apple];
expected += apple.price.doubleValue;
[scanner scanObject:apple];
[scanner scanObject:banana];
expected += banana.price.doubleValue;
[scanner scanObject:banana];
[scanner scanObject:banana];
expected += banana.price.doubleValue;
double result = [[scanner checkOut] doubleValue];
STAssertEquals(expected, result, @"Expected %g, but returned %g", expected, result);
}

Application

 -(PriceScanner*)init {  
self.total = [NSNumber numberWithDouble:0.0];
self.list = [[NSMutableArray alloc] init];
return self;
}
-(void)scanObject:(Item*)item {
[self.list addObject:item];
}
-(void)scanItem:(NSString*)item {
if ([item isEqualToString:@"apple"]){
self.total = [NSNumber numberWithDouble:[self.total doubleValue] + 0.6];
}
if ([item isEqualToString:@"banana"]){
self.total = [NSNumber numberWithDouble:[self.total doubleValue] + 0.7];
}
if ([item isEqualToString:@"orange"]){
self.total = [NSNumber numberWithDouble:[self.total doubleValue] + 0.5];
}
}
-(void)applyBOGOF{
double newTotal = [self.total doubleValue];
NSMutableSet *tempSet = [[NSMutableSet alloc] init];
for (Item* i in self.list){
if ([tempSet containsObject:i.description]){
newTotal -= [i.price doubleValue];
[tempSet removeObject:i.description];
} else {
[tempSet addObject:i.description];
}
}
self.total = [NSNumber numberWithDouble:newTotal];
}
-(NSNumber*)checkOut {
double newTotal = [self.total doubleValue];
for (Item* i in self.list){
newTotal += i.price.doubleValue;
}
self.total = [NSNumber numberWithDouble:newTotal];
[self applyBOGOF];
return self.total;
}

Test Driven Development: Prologue.

I’m putting down, for the record, that I’m going to endeavour to do more testing in my software. I plan to do this by means of Test Driven Development. While I plan to do as much work as possible in Xcode probably using GHUnit or OCUnit, visual studio is proving inescapable so I’ll probably be using NUnit as well.

What I already know.

I went for an interview for an industrial placement at a software company that uses Extreme Erogramming with TDD for all of their software. Now, I’m a big fan of XP and this has proven hugely successful for this company so I paid attention to their processes.

  1. Write the test. Make sure you give it a name that describes what the test is for.
  2. Fail the test. This was really important, especially as the application starts to grow, because new tests won’t always fail, but they should. If you skip this step you can end up wasting a lot of time trying to fix a problem that isn’t there.
  3. Get the test to pass as quickly as possible. This is also very important because it stops you spending hours trying to implement the most ideal solution and losing scope of what you’re trying to do. Literally just throw in whatever you can to get that test to pass.
  4. Refactor. Make it look nice, now is that time to maybe think about the ideal implementation, but not too much.

This approach gets a lot of criticism from people saying that it promotes bad design, or poorly written code. But the idea behind TDD is that you’re focussing on a single problem at a time. So when you start, yeah you have a bunch of really inflexible code and virtually no design in place. But as the number of tests grows and likewise the application, better design falls right into place and you end up with a very robust piece of software.

Anyway, I’ll see how I get on. It may be a bit naive of me to just be saying All my products will be TDD from now on but I’m going to give it a go and see how far I get.