TDD Exercise – A StringCalculator in Objective-C

Introduction

Update: You can now get the latest source from GitHub at https://github.com/dmeehan1968/StringCalculator.

I’ve been struggling over the last couple of weeks with how to construct a new UIControl for iOS. It’s moderately complex and I keep getting lost and coding my way up blind alleyways. The principle problem is that I’m not breaking down the problem into its areas of responsibility. Add in the fact that some of the existing controls and MVC patterns employed in the iOS API are a little muddied, and it becomes hard to know where to start. Looking for online help for this sort of problem is not easy, because everyone has their own unique problems to solve.

As a result I thought it would be a good exercise to do some practice, using Test Driven Development (TDD) to drive out the design. The biggest issue I think I have is that I tend to want to write code in pursuit of the obvious solution, but not one that is adaptable as the overall solution develops. My experience with Object Oriented Design doesn’t go much beyond simple encapsulation and inheritance so my familiarity with various design patterns is low.

Whilst reading up a bit on TDD and OOD practice, I came across a great piece about using TDD to implement a String Calculator in C#. It didn’t click at first, I just developed a single monolithic solution in much the same way that Dave did, and reading the description of the alternative approach using BDD didn’t really register. My lack of experience with C# was partly to blame, but the penny wasn’t dropping. I went back to work on the UIControl, took a slightly different perspective, but still ended up with some very smelly code and no real progress.

On reflection, my big problem was the Single Responsibility Principle – each fixture should be responsible for only one thing. The String Calculator kata is a coding exercise to see if you can use TDD to drive out an appropriate design. It’s not intended for any given language but there are links on the page to different approaches. Unfortunately, the link for the Objective C example is no longer working, so I was in the dark, but that might have been a good thing.

I’m going to reproduce the exercise here, starting with:

Requirements for a StringCalculator

I’m going to lay out the process that I used so that you can see how I arrived at my solution. By not reading ahead you are simulating how the requirements for a fixture can develop over time, so you are not inclined to try and second guess what might be coming later. This helps you to adopt the YAGNI principle (You Aren’t Gonna Need It) of TDD – only develop what is necessary to meet a failing test.

Bear in mind that I had already taken a stab at it, and implemented a single class to encapsulate the requirements, and gotten into a slight mess over the string parsing largely because of a lack of separation. This time was a deliberate attempt as enforcing good Object Oriented Design as part of the process, and while for a relatively trivial exercise as this, it may appear at first to be somewhat long winded, I hope it will give you a similar ‘Eureka’ moment of understanding that it did for me.

I’m using Kiwi as my TDD/BDD tool of choice for writing the tests, but you can use whatever you are comfortable with.

Here we go with each requirement in turn…

Create a simple String calculator with a method int Add(string numbers)

Lets start by writing a spec in Kiwi to meet that first requirement.

#import "Kiwi.h"

SPEC_BEGIN(StringCalculatorSpec)

describe(@"String Calculator", ^{

    __block StringCalculator *sut;

    beforeEach(^{

        sut = [[StringCalculator alloc] init];

    });

    afterEach(^{

        sut = nil;
    });

    it(@"should exist", ^{

        [sut shouldNotBeNil];

    });

});

SPEC_END

That’s all we need to prove the existence of our embryonic calculator, but this won’t compile because we haven’t created the class yet. I use sut as the variable for whatever I’m testing, kind of like self – I find this easier to remind me where the focus of development is. I also have a code template setup to generate the above as a starting point whenever I create a new spec. Whilst the existence test is a little pedantic, it doesn’t hurt.

Here is the outline .h for the StringCalculator class

#import < Foundation/Foundation.h >

@interface StringCalculator : NSObject

@end

And here is the outline .m for the StringCalculator class

#import "StringCalculator.h"

@implementation StringCalculator

@end

To make the existence test pass, we’ll add the .h to the spec file:

#import "kiwi.h"
#import "StringCalculator.h"

...

You should now be able to compile and pass the test.

Back to the requirement, we are asked to provide a method that takes a string of numbers and returns the result of adding those numbers together. Lets write the test to demonstrate the requirement.

it(@"add method should exist", ^{

    NSString *numberString = @"";
    NSInteger expectedResult = 0;

    NSInteger actualResult = [sut add: numberString];

    [[theValue(actualResult) should] equal: theValue(expectedResult)];

});

Note that I’ve included placeholders for the argument and expected result. Not reading ahead means we don’t know what to pass as an argument, other than a string, and that it should provide a numeric result.

Before we can compile, we need to add this to our class interface:

-(NSInteger)add: (NSString *) numberString;

And the implementation:

-(NSInteger)add:(NSString *)numberString {

    return 0;

}

This compiles and passes the test. The add method exists with the specified interface. Lets move on and look at the next part of the requirements

The Method can take 0, 1 or 2 numbers, and will Return their Sum

(for an empty string it will return 0) for example “” or “1” or “1,2”

Our previous test actually implements the first part of the requirement, e.g. for an empty string to return 0. Rather than write another test, we’ll just rename the test:

it(@"should return 0 for an empty string", ^{
...

Now, before we get carried away with writing code within our StringCalculator class, let’s think about the requirements and how we might design it with the Single Responsibility Principle in mind.

Applying the Single Responsibility Principle

If we look at the requirements so far we can deduce that we need a way of parsing numbers from a string, and performing some math to sum the result. So we have two concerns for our StringCalculator, and this suggests that we should split those concerns into separate responsibilities. Our StringCalculator then takes on its own third concern, the responsibility of managing the flow of information. There is no need at the moment to implement either the parser or the math fixtures – we only need to define the interface to them. We can then use mock objects to allow us to test the interface. No parsing or math need be done, just enough code to allow it to compile and test.

This approach is also good Object Oriented Design – Loose Coupling inclines the components of a system to be reusable as each has little or no knowledge of the other components in the system. In our StringCalculator, we are hoping to arrange the components so that only the StringCalculator component has any knowledge of the other components.

Improving Testability with Dependency Injection

Because we are not going to implement our parser or math class yet, we need to use Dependency Injection as a technique to allow us to test the principle. Whilst there is no requirement to allow our String Calculator to be configurable, we do need this for testing purposes. As its a design pattern that helps to decouple our object implementations, I think its well worth the effort of creating it. As our classes near production quality, we can always create a convenience constructor to simplify its use.

Whilst there are many ways of injecting dependencies, I’m going to go with a designated initialiser. We can modify our test setup so that we provide the required mock objects.

__block StringCalculator *sut;
__block id < NumberParser > numberParser;
__block id < Mathematics > mathematics;

beforeEach(^{

    numberParser = [KWMock mockForProtocol:@protocol(NumberParser)];

    mathematics = [KWMock mockForProtocol:@protocol(Mathematics)];

    sut = [[StringCalculator alloc] initWithNumberParser: parser andMathematics: mathematics];

    });

I’ve decided to define two protocols NumberParser and Mathematics which will provide the interfaces. In effect, these are delegates which will take care of their areas of responsibility.

In my test setup, I’ve created mock objects based on the protocol. There is no concrete implementation of these interfaces at the moment, and as we are currently developing ‘Outside-In‘ (‘Top-Down’ or ‘Skinning the Onion’ if you like) we can’t make use of the something that is yet to exist. However, by defining what it will look like, we can create a mock that ‘pretends’ to be that thing. It has no functionality, so we must define its inputs and outputs, but it will allow us to test the responsibility of our concrete object, the StringCalculator.

It’s important to remember the shift in focus now – we are no longer dealing with the specific requirements of parsing and summing, but with the StringCalculator’s responsibility to coordinate those activities.

Using protocols in this way means that we can continue to test our StringCalculator design, refactoring where necessary, without the need to rework production code. It also means that only our StringCalculator component has any knowledge of the collaborating objects in the design – the collaborators do not require any knowledge of the components which they are collaborating with. This is good design, keeping the coupling as loose as possible.

Let’s create a .h file that will contain our protocol definition, NumberParser.h:

#import < Foundation/Foundation.h >

@protocol NumberParser < NSObject >

-(NSArray *)parseString: (NSString *) numberString;

@end

Note that for now, our .h only includes the definition of the protocol, but later, once we get around to implementing the concrete implementation, its purpose will expand.

We’ll now do the same for the Mathematics protocol, in Mathematics.h:

#import < Foundation/Foundation.h >

@protocol Mathematics < NSObject >

-(NSInteger)sum: (NSArray *) numberArray;

@end

We now need to redefine our designated initialiser for the StringCalculator class. Here is the revised StringCalculator.h:

#import < Foundation/Foundation.h >
#import "NumberParser.h"
#import "Mathematics.h"

@interface StringCalculator : NSObject

@property (weak) id < NumberParser > numberParser;
@property (weak) id < Mathematics > mathematics;

-(id)initWithNumberParser: (id < NumberParser >)numberParser andMathematics : (id < Mathematics >) mathematics;
-(NSInteger)add:(NSString *)numberString;

@end

In this revision, we’ve defined the designated initialiser as initWithNumberParser:andMathematics: and added properties to hold references to the parser and mathematics delegates.

And here is our implementation for the initialiser:

-(id)initWithNumberParser:(id < NumberParser > )numberParser andMathematics:(id < Mathematics > )mathematics {

    if (self = [super init]) {
        _numberParser = numberParser;
        _mathematics = mathematics;
    }

    return self;
}

We should now be back at code that will build and pass the tests.

YAGNI? Really?

Now I know that some by now will be seriously questioning my logic here. Do we really need to get involved with dependency injection, protocols and mock objects for things that we’ll surely be implementing any time now? Are the requirements going to get any more complicated in the future? And ok, I’ll admit to cheating a bit here. In fact, I’m only saving you are bit of time by not showing how you would have already written more code and refactored to get to this solution. As I did in my first iteration, I went down a dark and smell alley of code before I realised there was likely to be a better way. The object of this exercise is not really about TDD in its purest sense, but in how to apply more than one of the rules of good development practice at the same time.

Dependency Injection makes code more testable, and more flexible to future change. Right now we are writing test code, and therefore we have a current technical requirement to make our code testable and the design to be high quality, even if our functional requirements don’t ask for any of this. So I’m happy to be putting in a little more effort. The effort is not really that much overhead as you write code, it just seems that way as I long-windedly try and explain it!

StringCalculators Single Responsibility – Coordinating Actions

Now that we have our interfaces, our tests can be refactored so that we prove that the internal workings match expectations. We expect StringCalculator to take a string of zero, one or two numbers (arbitrarily separated by comma), parse the string into numbers and then return the sum. We can now set expectations on our mock objects to prove this.

Again, the initial test can be replaced with a new test that proves the flow through the delegates:

it(@"should coordinate parsing and summing", ^{

    NSString *numberString = @"1,2";
    NSArray *numberArray = @[ @1, @2 ];
    NSInteger expectedResult = 3;

    [[numberParser should] receive:@selector(parseString:)
                         andReturn:numberArray
                     withArguments:numberString];

    [[mathematics should] receive:@selector(sum:)
                       andReturn:theValue(expectedResult)
                   withArguments:numberArray];

    NSInteger actualResult = [sut add: numberString];

    [[theValue(actualResult) should] equal: theValue(expectedResult)];

});

I’ve modified the test data to be more reflective of the requirements, but actually this is a moot point, because all we are interested in is whether objects of the right type and value are passed through the parser and the mathematics fixtures.

You’ll be pleased to hear that we are now finished with the StringCalculator fixture, there is nothing more to do at this point. We’ve demonstrated that it meets the requirements, now that we’ve abstracted the actual parsing and summing out to separate fixtures.

Start with the Simplest Test Case of an Empty String and Move to one and two Numbers

  • Remember to solve things as simply as possible so that you force yourself to write tests you did not think about
  • Remember to refactor after each passing test

So now we can move onto the meat and potatoes of the NumberParser. We can return to simple first principles now that we have the architectural issues out of the way for the time being. As our exercise specifies, we should start with simple cases and progress. I’ve grouped a couple of the points from the requirements because they logically belong together here and serve as a reminder.

Our first step is going to be to create a test specification for our NumberParser that allows us to bring it into existence.

#import "Kiwi.h"
#import "NumberParser.h"

SPEC_BEGIN(NumberParserSpec)

describe(@"Number Parser", ^{

    __block NumberParser *sut;

    beforeEach(^{

        sut = [[NumberParser alloc] init];

    });

    afterEach(^{

        sut = nil;
    });

    it(@"should exist", ^{

        [sut shouldNotBeNil];

    });

});

SPEC_END

In order for this to compile, we need to open up our existing NumberParser.h which only contained the protocol definition, and add in an interface definition for the concrete NumberParser class (it’s quite normal to write a protocol with the same name as its class interface, take a look at NSObject for example).

#import < Foundation/Foundation.h >

@protocol NumberParser < NSObject >

-(NSArray *)parseString: (NSString *) numberString;

@end

@interface NumberParser : NSObject < NumberParser >

@end

We can now also create NumberParser.m for the first time in order to put in our stub for the implementation:

#import "NumberParser.h"

@implementation NumberParser

-(NSArray *)parseString:(NSString *)numberString {

    return @[];

}

@end

That builds and passes the tests, so we should look to implement the first step, which is to give a zero result if passed an empty string. Given that we are only concerned with parsing at this point, we only need to ensure that an empty string returns no numbers in the array.

it(@"should return no numbers for an empty string", ^{

    NSArray *expectedResult = @[];
    NSArray *actualResult = [sut parseString:@""];

    [[actualResult should] equal:expectedResult];
});

I cheated a little there and implemented a stub for parseString that actually passed the first test, so no more production code to implement without another test:

it(@"should return one number for a string with one number", ^{

    NSArray *expectedResult = @[ @1 ];
    NSArray *actualResult = [sut parseString:@"1"];

    [[actualResult should] equal: expectedResult];
});

Which requires us to write some production code. We need to refactor to keep the first test (empty string) working, and implement something that copes with one number in the string.

-(NSArray *)parseString:(NSString *)numberString {

    if ([numberString isEqualToString:@""]) {
        return @[];
    }

    return @[[NSNumber numberWithInteger:[numberString integerValue]]];

}

If its not obvious what the last line is doing, we are converting the string to an integer value, putting that in an NSNumber so that we can store it in an NSArray.

Our next test is to see if we can handle two numbers separated by comma.

it(@"should return two numbers for a string with two numbers", ^{

    NSArray *expectedResult = @[ @1, @2 ];
    NSArray *actualResult = [sut parseString:@"1,2"];

    [[actualResult should] equal: expectedResult];
});

After refactoring parseString we have:

-(NSArray *)parseString:(NSString *)numberString {

    if ([numberString isEqualToString:@""]) {
        return @[];
    }

    NSArray *substringArray = [numberString componentsSeparatedByString:@","];

    NSMutableArray *numberArray = [NSMutableArray arrayWithCapacity:substringArray.count];

    [substringArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

        [numberArray addObject:[NSNumber numberWithInteger:[obj integerValue]]];

    }];

    return [numberArray copy];

}

At this point we have completed the basic requirements for our NumberParser. I’m going to stick with this fixture for the time being and leave the maths till later.

Allow the Add method to handle an Unknown Amount of Numbers




Oh look, there is nothing to do here, except for writing a test to prove that our implementation is already capable of this. If you ask me, the term ‘unknown’ in the requirement is a bit vague, but I suspect that they meant ‘arbitrary’. I’ve also taken the liberty of using some multi-digit numbers this time – it occurs to me that the NumberParser could fail to group consecutive digits together to form a single number, and this should catch it if it happens.

it(@"should return numbers for a string with an arbitrary set of numbers", ^{

    NSArray *expectedResult = @[ @1, @20, @300, @4000, @50000 ];
    NSArray *actualResult = [sut parseString:@"1,200,300,4000,50000"];

    [[actualResult should] equal: expectedResult];
});

Allow the Add method to Handle New Lines Between Numbers (instead of commas).

  • The following input is ok: 1\n2,3 (will equal 6)
  • The following input is NOT ok: 1,\n (not need to prove it – just clarifying)

Again, I’m querying the requirements here because it says ‘instead’ but the example implies ‘as well as’. Or is that me being picky?

it(@"should allow newlines as well as commas as separators", ^{

    NSArray *expectedResult = @[ @1, @2, @3 ];
    NSArray *actualResult = [sut parseString:@"1n2,3"];

    [[actualResult should] equal: expectedResult];
});

Thats our test to prove the requirement, and here is the change to parseString, where we replace componentsSeparatedByString: with componentsSeparatedByCharactersInSet:.

NSCharacterSet *delimiters = [NSCharacterSet characterSetWithCharactersInString:@"n,"];

NSArray *substringArray = [numberString componentsSeparatedByCharactersInSet:delimiters];

We don’t need to test for invalid input, as the requirement earlier allowed us to ignore those cases.

Support Different Delimiters

  • To change a delimiter, the beginning of the string will contain a separate line that looks like this: //[delimiter]\n[numbers…] for example //;\n1;2 should return three where the default delimiter is ; .
  • The first line is optional. All existing scenarios should still be supported
it(@"should allow a different delimiter to be specified within the string", ^{

    NSArray *expectedResult = @[ @1, @2 ];
    NSArray *actualResult = [sut parseString:@"//;n1;2"];

    [[actualResult should] equal: expectedResult];
});

Given that newline is a legitimate delimiter, we can just check to see if the first element starts with double-slash, extract the new delimiter and then reprocess the input string with the first component stripped off.

// After componentsSeparatedByCharactersInSet: ...
NSString *firstString = substringArray[0];

if (firstString.length > 2 && [[firstString substringToIndex:2] isEqualToString:@"//"]) {

    delimiters = [NSCharacterSet characterSetWithCharactersInString:[firstString substringFromIndex:2]];

    substringArray = [[numberString substringFromIndex:firstString.length+1] componentsSeparatedByCharactersInSet:delimiters];

}

I want to put some error checking in there but as the requirements don’t want it, I’ll put up with the obvious code smells!

Calling Add with a Negative Number will Throw an Exception

With message “negatives not allowed” and the negative that was passed. If there are multiple negatives, show all of them in the exception message

So lets try a test that includes two negatives as well as some positive numbers.

it(@"should raise an exception if a negative number is passed, including each negative number encountered", ^{

    [[theBlock(^{
        [sut parseString:@"1,2,-3,-4,5"];
    }) should] raiseWithName:@"NSInternalInconsistencyException" reason:@"negatives not allowed -3,-4"];

});

And we can refactor parseString so that it includes some checking for negatives:

// After mutableArrayWithCapacity: ...
NSMutableArray *negatives = [NSMutableArray array];

[substringArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

    NSNumber *number = [NSNumber numberWithInteger:[obj integerValue]];

    if (number.integerValue < 0) {

        [negatives addObject: number ];

    } else {

        [numberArray addObject: number];
    }

}];

if (negatives.count > 0) {

    [NSException raise:@"NSInternalInconsistencyException"  format: @"negatives not allowed %@", [negatives componentsJoinedByString:@","] ];
}

return [numberArray copy];

Stop Here if you are a Beginner

Continue if you can finish the steps so far in less than 30 minutes.

Aww come on, we’re TDD, Single Responsibility Principle, Loose Coupling, Dependency Injecting ninja’s by now. Let’s not worry about the timing, this is a lengthy explanation and an expansion on the original exercise, as we are trying to write quality code, not just something that works. Also, I’m already looking at that next requirement and thinking that we can do a lovely bit of refactoring, as I’m starting to smell more than one responsibility. Let’s continue…

Numbers Bigger than 1000 should be Ignored, so Adding 2 + 1001 = 2

Our last step was to filter out negative values, but I’m not comfortable with the idea that filtering and parsing are actually a single concern. I could let it slide before, but now that we are presented with another filtering behaviour, I think we need to do something about that.

So our design conundrum is where should this filtering go? If its not a responsibility of string parsing, then is it a responsibility of the mathematics? Don’t smell like it does it! So, if it isn’t a responsibility of parsing or maths, then is it a responsibility of StringCalculator? Maybe, but that is already holding the coordinators responsibility, and therefore that leaves us with no choice but to move filtering into a fixture of its own.

Refactoring With Confidence

Although we are going to move on fairly quickly and implement our filter fixture, lets use the same process as we did at the start and give ourselves the flexibility to test StringCalculator without a concrete implementation of the filter. Our first step is to revisit our test specification for StringCalculator and refactor the setup and tests so that we are mocking our filter interface. I’m only showing the refactored test here, there are a number of edits needed elsewhere, such as refactoring the designated initialiser for StringCalculator, but I’m assuming you can figure that out for yourselves.

One important benefit of TDD is that refactoring can be done in a managed way. You’ll see over this next section that I can take reasonably small steps, stopping along the way to re-run the tests and ensure that things are still working. If something goes wrong, I should back out the last change and try again. If you use some sort of version control system or snapshotting facility in Xcode, then this can be really easy to do. I wish I had to habit to do it far more often than I do!

Notice that we still have only two tests in our StringCalculatorSpec (existence and this one) as our only concern for StringCalculator is that the plumbing works as expected.

it(@"should coordinate parsing, filtering and summing", ^{

    NSString *numberString = @"1,2,-3";
    NSArray *unfilteredNumberArray = @[ @1, @2, @-3 ];
    NSArray *filteredNumberArray = @[ @1, @2 ];
    NSInteger expectedResult = 3;

    [[numberParser should] receive:@selector(parseString:)
                         andReturn:unfilteredNumberArray
                     withArguments:numberString];

    [[numberFilter should] receive:@selector(filter:) andReturn:filteredNumberArray withArguments:unfilteredNumberArray];

    [[mathematics should] receive:@selector(sum:)
                       andReturn:theValue(expectedResult)
                   withArguments:filteredNumberArray];

    NSInteger actualResult = [sut add: numberString];

    [[theValue(actualResult) should] equal: theValue(expectedResult)];

});

I’ve changed the test so that we now make sure that the number array is correctly passed from the parser to the filter and onto the mathematics. Here is the definition of the NumberFilter protocol in NumberFilter.h

#import < Foundation/Foundation.h >

@protocol NumberFilter < NSObject >

-(NSArray *)filter: (NSArray *)unfilteredArray;

@end

Once done, without any need to refactor the NumberParser yet (so its still doing the filtering of negatives), we can run our tests and prove that the refactoring has not broken anything. This really helps to demonstrate just how powerful Test Driven Development can be in satisfying you that refactoring doesn’t break existing functionality. We’ve just made a significant change to the interfaces to our calculator, and we need to know that things work as they did before we move on.

Satisfied that nothing it broken, we can bring the NumberFilter concrete implementation into existence. I’m not going to show you the stub for the NumberFilterSpec.m as it should be apparent that this brings an instance into being as expected. What I do want to show is the initial implementation of the filter: method:

-(NSArray *)filter:(NSArray *)unfilteredArray {

    return unfilteredArray;
}

A stub, but it returns the unfiltered array by default, which means that all of our existing tests still pass. Remember, keep taking small steps, fail, pass, refactor as we move towards the final solution.

Our job now is to refactor the NumberParser as the filtering of negatives now has a proper home to go to. We can start by moving the test we created in NumberParserSpec.m that checks for an exception when there are negatives, and put that in NumberFilterSpec.m.

it(@"should raise an exception if a negative number is passed, including each negative number encountered", ^{

    [[theBlock(^{
        [sut filter:@[ @1, @2, @-3, @-4, @5 ]];
    }) should] raiseWithName:@"NSInternalInconsistencyException" reason:@"negatives not allowed -3,-4"];

});

I made a small change to the code within the block so that its using filter: and passing an appropriate array of numbers including the negatives to test for. Changing the context of the test has changed the interface its using, but the principle remains the same.

-(NSArray *)filter:(NSArray *)unfilteredArray {

    NSMutableArray *negativesArray = [NSMutableArray array];

    NSMutableArray *filteredArray = [NSMutableArray arrayWithCapacity:unfilteredArray.count];

    [unfilteredArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

        if ([obj integerValue] < 0) {

            [negativesArray addObject:obj];

        } else {

            [filteredArray addObject:obj];

        }
    }];

    if (negativesArray.count > 0) {

        [NSException raise:@"NSInternalInconsistencyException"  format: @"negatives not allowed %@", [negativesArray componentsJoinedByString:@","] ];
    }

    return filteredArray;
}

The parseString: method gets simplified back to how it was before we added the negative filtering, and filter: gets filled out with code to check for those nasty negatives. We should be back at a green light, and so now we can readdress the new requirement, which was to ignore values great than 1000. Let’s setup an expectation for that:

it(@"should ignore values greater than 1000", ^{

    NSArray *unfilteredArray = @[ @1000, @1001 ];
    NSArray *expectedResult = @[ @1000 ];
    NSArray *actualResult = [sut filter:unfilteredArray];

    [[actualResult should] equal:expectedResult];
});

In this test I’ve included 1000 and 1001, as that acts as a validation that the filtering does allow for a value of 1000. We’ve already tested values around zero in the previous test. These edge cases need testing, as its easy to confuse or alter a less-than-or-equal-to comparison to just a less-than without realising, so the test is of high value.

We only need to make a change to a single line of code in the NumberFilter to make this pass, but I’ll include the context for you:

@!objectivec
if ([obj integerValue] < 0) {

    [negativesArray addObject:obj];

} else if ([obj integerValue] <= 1000) {

    [filteredArray addObject:obj];

}

Delimiters can be of any Length

With the following format: //[delimiter]\n for example: //[***]\n1***2***3 should return 6

I’m making an assumption here that the requirement is to maintain the previous delimiter support as well, although its not explicit about this. Seems reasonable though.

Delving back into NumberParser for this requirement, we can create the following test to set the expectation:

it(@"should allow multi-character delimiters surrounded by square brackets", ^{

    NSArray *expectedResult = @[ @1, @2, @3 ];
    NSArray *actualResult = [sut parseString:@"//[***]n1***2***3"];

    [[actualResult should] equal: expectedResult];

});

And then an extension to the parseString method in NumberParser.m so that we check for the syntax and split the string appropriately:

if (firstString.length > 2 && [[firstString substringToIndex:2] isEqualToString:@"//"]) {

    if ([[firstString substringWithRange:NSMakeRange(2,1)] isEqualToString:@"["] && [[firstString substringFromIndex:firstString.length-1] isEqualToString:@"]"]) {

        NSString *stringDelimiter = [firstString substringWithRange:NSMakeRange(3, firstString.length-4)];

        substringArray = [[numberString substringFromIndex:firstString.length+1] componentsSeparatedByString:stringDelimiter];

    } else {

        delimiters = [NSCharacterSet characterSetWithCharactersInString:[firstString substringFromIndex:2]];

        substringArray = [[numberString substringFromIndex:firstString.length+1] componentsSeparatedByCharactersInSet:delimiters];

    }

}

Smell Anything Unusual?

I’ll have to admit, the parser code is looking like it could do with some refactoring. It’s quite possible that it could be more succinctly handled with a regular expression, but I have to say I hate regex syntax and only like to get into it when there is a higher requirement. Remember also that regular expressions can be expensive to use from a performance perspective. I did experiment with regex before coming up with a mod to the existing code, and the regex wasn’t working out because it involves either repeated back references (which don’t do what you want) or splitting the parsing into more than one attempt (getting the delimiters and then getting the numbers).

Allow Multiple Delimiters

Like this: //[delim1][delim2]\n for example //[*][%]\n1*2%3 should return 6.

Setting up the spec for multiple delimiters is easy enough:

it(@"should allow multiple delimiter strings each surrounded by square brackets", ^{

    NSArray *expectedResult = @[ @1, @2, @3 ];
    NSArray *actualResult = [sut parseString:@"//[*][%]n1*2%3"];

    [[actualResult should] equal: expectedResult];

});

Refactoring parseString to cope with the requirement was a little more involved and required a recursive method to drill into the strings with the delimiters. There might still be a cleaner approach to this, but it doesn’t require major refactoring of the existing code, so that clinches it for me.

@!objectivec
-(NSArray *)splitStringsInArray: (NSArray *) stringsArray withStringDelimitersInArray: (NSArray *) delimitersArray {

    NSMutableArray *splitStringsArray = [NSMutableArray array];

    NSString *delimiter = delimitersArray[0];

    NSMutableArray *remainingDelimiters = [delimitersArray mutableCopy];
    [remainingDelimiters removeObject:delimiter];

    [stringsArray enumerateObjectsUsingBlock:^(NSString *stringToSplit, NSUInteger idx, BOOL *stop) {

        NSArray *components = [stringToSplit componentsSeparatedByString:delimiter];

        if (components.count > 1 && remainingDelimiters.count > 0) {

                [splitStringsArray addObjectsFromArray:[self splitStringsInArray: components withStringDelimitersInArray: [remainingDelimiters copy]]];

        } else {

            [splitStringsArray addObjectsFromArray:components];
        }

    }];

    return [splitStringsArray copy];

}

This is called from into parseString when string delimiters are detected, instead of just returning the single instance.

NSArray *delimiters = [stringDelimiter componentsSeparatedByString:@"]["];

substringArray = [self splitStringsInArray: @[[numberString substringFromIndex:firstString.length+1]]withStringDelimitersInArray: delimiters];

Wow! Finally we get to the bottom of parsing!

I’m still inclined to look at the parseString implementation and think that there might be a cleaner way, but we have no more requirements to satisfy, and so that might be a moot point. If someone were to come along and query the performance, or ask for another feature request, then that might be the time to start looking again at the design. Anything else for now falls into YAGNI territory!

And Not Forgetting That We Are Supposed To Be Doing Some Maths!

We’ve put this off for what seems like an age, but we are now in a position to implement the maths. This should be pretty simple, as we just need a couple of simple number array examples to prove that the maths does what is expected of it.

First off, we need a stub spec so we are ready to implement, and after checking for existence, here is the first real test:

it(@"should return zero for no numbers in array", ^{

    NSInteger actualResult = [sut sum:@[] ];

    [[theValue(actualResult) should] equal:theValue(0)];

});

And here is the stub implementation:

-(NSInteger)sum:(NSArray *)numberArray {

    return 0;
}

A quick test with a single number array for starters:

@!objectivec
it(@"should return value of one number from array", ^{

    NSInteger actualResult = [sut sum:@[ @123 ] ];

    [[theValue(actualResult) should] equal: theValue(123)];

});

And a quick refactoring of the sum: method to cope with that:

-(NSInteger)sum:(NSArray *)numberArray {

    __block NSInteger sum = 0;

    [numberArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {

        sum += [obj integerValue];

    }];

    return sum;
}

And with a bit of luck, one final spec with multiple values to make sure:

it(@"should return the sum of multiple number values in array", ^{

    NSInteger actualResult = [sut sum:@[ @1, @2, @3, @4 ] ];

    [[theValue(actualResult) should] equal: theValue(10)];

});

Excellent! All tests pass and we should be just about done.

One final thing to think about – does our StringCalculatorSpec, given that it only implements blocks, prove that the solution works as intended? Well, in itself it doesn’t appear to show proof, at least not in a way that the casual observer would see at first glance. You’d need to go away and ensure that the specs for NumberParser, NumberFilter and Mathematics all did their jobs as intended.

This final level of check is what could be considered ‘Integration Testing’ – or in other words, do the sum of the parts add up to the whole (no pun intended here folks!)

Just to satisfy curiosity, lets just do some quick tests in StringCalculatorSpec.m to make sure:

context(@"integration tests", ^{

    beforeEach(^{

        numberParser = [NumberParser new];
        numberFilter = [NumberFilter new];
        mathematics = [Mathematics new];
        sut = [[StringCalculator alloc] initWithNumberParser:numberParser andMathematics:mathematics andFilter:numberFilter];

    });

    it(@"should sum numbers from a string", ^{

        NSInteger actualResult = [sut add:@"1,2,3,4"];

        [[theValue(actualResult) should] equal:theValue(10)];

    });

    it(@"should ignore values over 1000", ^{

        NSInteger actualResult = [sut add:@"1000,1001"];

        [[theValue(actualResult) should] equal:theValue(1000)];

    });

    it(@"should raise an exception if there is a negative number", ^{

        [[theBlock(^{
            [sut add:@"-1"];
        }) should] raise];

    }); 
});

I first wrapped the existing tests into a context block so that the beforeEach setup for them was isolated, and then added the above context block to utilise the concrete implementations of the interfaces. The tests are simplified and just illustrate some of the typical uses that the solution is expected to provide.

Conclusion

There is something of a ‘documentation’ issue with this approach in that the spec now don’t provide a top-down readable version of the technical interpretation of the requirements, but this is not actually something that I think Kiwi is designed to provide. If we want that sort of thing, then we’d need to look towards solutions like Cucumber and its principle of Executable Specifications.

I’ve found this a worthwhile exercise, both in the first failed implementation and in the reworking and retelling of it. It’s given me a much clearer understanding of how the design of the system can help to simplify and clarify the requirements, and hopefully how to avoid quite so many blind alleys in other areas.

I’m still stuck with the ‘right’ implementation of my custom UIControl, but it was worth taking a few hours out to do this exercise and get a fresh perspective. I’ll be sure to share the control and lessons learnt when I do finally get the better of it!

Source Code

Update: You can now get the latest source from GitHub at https://github.com/dmeehan1968/StringCalculator.

Postscript – A Better NumberParser

Being curious as I am, I couldn’t let that bad smell emanating from parseString get the better of me. As a result I went ahead and created an alternate version based on NSScanner.

There’s not actually much difference in terms of how it operates and it runs to about the same number of lines of code, but it looks a hell of a lot more readable which I’d say is a worthwhile improvement. It even made it easier to add a little exception raising in the event of a malformed input string.




4 thoughts on “TDD Exercise – A StringCalculator in Objective-C

  1. Quiggles

    I have absolutely no idea what you’re talking about it! Not bad for a bloke that copied my maths at school! Keep at it 🙂

    Reply
  2. Shantanu Dutta

    Awesome tutorial. Both this one and the other tutorial giving an introduction in Kiwi. Got to learn a lot. But am seriously messing up big time with asynchronous testing and testing involving network calls. Not able to find any good tutorial like yours giving a simple explanation.

    Reply
  3. Lucian

    Great tutorial, but i think it could be updated to cast the numberParser and mathematics to id because a beginner will not know what “No instance method for selector ‘attachedToVerifier:'” error is.

    [[(id)numberParser should] receive:@selector(parseString:) andReturn: numberArray withArguments: numberString];

        [[(id)mathematics should] receive: @selector(sum:) andReturn: theValue(expectedResult) withArguments: numberArray];
    

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *