Last time we got a simple iPhone project started in Xcode with a test target. We didn't do much more than build it so this time around we will write some code and attempt to understand it.
We started with the below template test class. Notice that you need to import a testing specific library from the SenTestingKit. In my last post we did the actual import of the framework itself, this single import statement felt like nothing more than a simple xunit like assembly being added to the project so we could use the assert methods without having to write them from scratch.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#import <SenTestingKit/SenTestingKit.h>
@interface SampleUnitTest : SenTestCase {
}
- (void) testString;
@end
@implementation SampleUnitTest
- (void) testString
{
STAssertEquals(@'hi', @'hi', @'the first string did not match the second');
}
@end
Next you will notice the @interface is declared above the @implementation. From my c# mind this didn't feel all that strange except that it's required. The interface (.h file) simply defines the 'what' and the implementation (.m file) defines the 'how'.
Since we have a test method called 'testString' we will need to declare this in the interface. Notice it doesn't have a return type so we mark it void. Similar to any xunit like test I've written in c# - we let the failures occur in the assert statements rather than return a boolean value.
In the implementation we provide a method that has a simple assert equals defined to show the string 'hi' is equal to the string 'hi'. The message to the right is only shown when the test fails.
One thing I found strange at first was that every time you do a build in Xcode of the test target it will not only compile the src but it will also run the tests. Don't belive me? Change the first string from 'hi' to 'bye' and do a build. It should fail now because the assert is invalid.
I said strange above only because I've never been able to do this in my c# projects because of performance issues. But as these tests are all running fast it makes sense to get the feedback sooner rather than later. Just something to make you aware of because the first time I had one of these failing I couldn't figure out the cryptic error message and thought it was related to a syntax error.
To get a better unit test for this string we should create a new string. The syntax for this is a little strange to be kind, but to create a new object of type string we do the following
1
NSString* str = [[NSString alloc] init];
What is actually happening on the right side of this statement is that we ask objective-c to set asside the physical memory for the string object in the first block using the alloc keyword. Next in the outside init call we simply call the init method on the NSString object and this will return a pointer that we can assign to our local variable 'str'.
I wouldn't expect anyone to 'like' this syntax at first, or even find this to look normal. It's a bit strange but to be clear it's not the only way to new up an object. You can also do something like NSString* str = [NSString new] but I quickly found that most experienced objective-c developers prefer the syntax listed above instead.
Next we need to alter the state of the object. A simple assignment statement will work.
1
str = @'hi';
Now we can alter the assert to compare a raw string to this object and see if it's working as expected.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#import <SenTestingKit/SenTestingKit.h>
@interface SampleUnitTest : SenTestCase {
}
- (void) testString;
@end
@implementation SampleUnitTest
- (void) testString
{
NSString* str = [[NSString alloc] init];
str = @'hi';
STAssertEquals(@'hi', str, @'done');
[str release];
}
@end
In the final implementation you can see that I only left out one line of code. The final line in the method [str release]. This is needed to release the memory we had allocated earlier in the [NSString alloc] line. I won't go into memory management here because it's a huge topic all it's own, but I will say that the rule of thumb around memory management is this:
If you create it, you need to release it. If you don't create it, don't attempt to manage it.
This was a simple exercise in how to unit test a string, but what about unit testing my own object? That's coming up in my next post so stay tuned!