Beginning Automated Testing With Xcode Part 2/2
Note from Ray: This is the tenth and final iOS 6 tutorial in the iOS 6 Feast! This tutorial comes from our new book iOS 6 By Tutorials. Charlie Fulton wrote this chapter – a friend of mine and one of the newest members of the Tutorial Team. Enjoy! This is a blog post by […] By Charlie Fulton.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Beginning Automated Testing With Xcode Part 2/2
35 mins
Testing a class with local JSON data
Let’s take a look at the Character class and add a test suite for it. Ideally, you would have added these tests as you were developing the class.
What if you wanted to test creating your Character class from local data? How would you do that? What if you wanted to share such data with each test case?
So far, you haven’t used two special methods that are available in your unit tests: setUp and tearDown. Every time you run your unit tests, each test case is invoked independently. Before each test case runs, the setUp method is called, and afterwards the tearDown method is called. This is how you can share code between each test case.
When building an app that relies on data from web services, I find it very helpful to create tests using data that matches the payload from the services. Quite often in a project, you will only be working on the client side and waiting for the services from another developer. You can agree on a format and can “stub” out the data in a JSON file. For this app, I downloaded some character and guild data to create tests for the model classes so I could flesh those out before working on the networking code.
First add your test data. Extract this AutoTestData.zip file and drag the resulting folder into your Xcode project. Make sure that Copy items into destination group’s folder (if needed) is checked, Create groups for any added folders is selected, and that the GuildBrowserLogicTests target is checked, and then click Finish.
Update 10/31/12: There were some questions on the forums about the exact command line instructions to use to set up Jenkins, so if you have any troubles check out this handy list of Jenkins shell steps text file I put together for you.
Update 10/31/12: There were some questions on the forums about the exact command line instructions to use to set up Jenkins, so if you have any troubles check out this handy list of Jenkins shell steps text file I put together for you.
Now add an Objective-C test case class for your Character test cases. In the Xcode project navigator, right-click on the GuildBrowserLogicTests group, choose New File\Cocoa Touch\Objective-C test case class, click Next, enter CharacterTests for the class name, click Next again, make sure only the GuildBrowserLogicTests target is selected, and then click Create.
Replace the contents of CharacterTests.m with:
#import "CharacterTests.h"
#import "Character.h"
#import "Item.h"
@implementation CharacterTests
{
// 1
NSDictionary *_characterDetailJson;
}
// 2
-(void)setUp
{
// 3
NSURL *dataServiceURL = [[NSBundle bundleForClass:self.class]
URLForResource:@"character" withExtension:@"json"];
// 4
NSData *sampleData = [NSData dataWithContentsOfURL:dataServiceURL];
NSError *error;
// 5
id json = [NSJSONSerialization JSONObjectWithData:sampleData
options:kNilOptions
error:&error];
STAssertNotNil(json, @"invalid test data");
_characterDetailJson = json;
}
-(void)tearDown
{
// 6
_characterDetailJson = nil;
}
@end
Hammer time, break it down! :]
- Remember that test classes can have instance variables, just like any other Objective-C class. Here you create the instance variable _characterDetailJson to store your sample JSON data.
- Remember that setUp is called before each test case. This is useful because you only have to code up the loading once, and can manipulate this data however you wish in each test case.
- To correctly load the data file, remember this is running as a test bundle. You need to send self.class to the NSBundle method for finding bundled resources.
- Create NSData from the loaded resource.
- Now create the JSON data and store it in your instance variable.
- Remember that tearDown is called after each test case. This is a great spot to clean up.
Make sure everything is loading up correctly by running Product\Test (⌘-U). OK, now you have your test class set up to load some sample data.
After the tearDown method, add the following code:
// 1
- (void)testCreateCharacterFromDetailJson
{
// 2
Character *testGuy1 = [[Character alloc] initWithCharacterDetailData:_characterDetailJson];
STAssertNotNil(testGuy1, @"Could not create character from detail json");
// 3
Character *testGuy2 = [[Character alloc] initWithCharacterDetailData:nil];
STAssertNotNil(testGuy2, @"Could not create character from nil data");
}
Break it down!
- Here you are creating test cases for the Character class designated initializer method, which takes an NSDictionary from the JSON data and sets up the properties in the class. This might seem trivial, but remember that when developing the app, it’s best to add the tests while you are incrementally developing the class.
- Here you are just validating that initWithCharacterDetailData does indeed return something, using another STAssert macro to make sure it’s not nil.
- This one is more of a negative test, verifying that you still got a Character back even though you passed in a nil NSDictionary of data.
Run Product\Test (⌘-U) to make sure your tests are still passing!
After the testCreateCharacterFromDetailJson method in CharacterTests.m, add the following:
// 1
-(void)testCreateCharacterFromDetailJsonProps
{
STAssertEqualObjects(_testGuy.thumbnail, @"borean-tundra/171/40508075-avatar.jpg", @"thumbnail url is wrong");
STAssertEqualObjects(_testGuy.name, @"Hagrel", @"name is wrong");
STAssertEqualObjects(_testGuy.battleGroup, @"Emberstorm", @"battlegroup is wrong");
STAssertEqualObjects(_testGuy.realm, @"Borean Tundra", @"realm is wrong");
STAssertEqualObjects(_testGuy.achievementPoints, @3130, @"achievement points is wrong");
STAssertEqualObjects(_testGuy.level,@85, @"level is wrong");
STAssertEqualObjects(_testGuy.classType, @"Warrior", @"class type is wrong");
STAssertEqualObjects(_testGuy.race, @"Human", @"race is wrong");
STAssertEqualObjects(_testGuy.gender, @"Male", @"gener is wrong");
STAssertEqualObjects(_testGuy.averageItemLevel, @379, @"avg item level is wrong");
STAssertEqualObjects(_testGuy.averageItemLevelEquipped, @355, @"avg item level is wrong");
}
// 2
-(void)testCreateCharacterFromDetailJsonValidateItems
{
STAssertEqualObjects(_testGuy.neckItem.name,@"Stoneheart Choker", @"name is wrong");
STAssertEqualObjects(_testGuy.wristItem.name,@"Vicious Pyrium Bracers", @"name is wrong");
STAssertEqualObjects(_testGuy.waistItem.name,@"Girdle of the Queen's Champion", @"name is wrong");
STAssertEqualObjects(_testGuy.handsItem.name,@"Time Strand Gauntlets", @"name is wrong");
STAssertEqualObjects(_testGuy.shoulderItem.name,@"Temporal Pauldrons", @"name is wrong");
STAssertEqualObjects(_testGuy.chestItem.name,@"Ruthless Gladiator's Plate Chestpiece", @"name is wrong");
STAssertEqualObjects(_testGuy.fingerItem1.name,@"Thrall's Gratitude", @"name is wrong");
STAssertEqualObjects(_testGuy.fingerItem2.name,@"Breathstealer Band", @"name is wrong");
STAssertEqualObjects(_testGuy.shirtItem.name,@"Black Swashbuckler's Shirt", @"name is wrong");
STAssertEqualObjects(_testGuy.tabardItem.name,@"Tabard of the Wildhammer Clan", @"nname is wrong");
STAssertEqualObjects(_testGuy.headItem.name,@"Vicious Pyrium Helm", @"neck name is wrong");
STAssertEqualObjects(_testGuy.backItem.name,@"Cloak of the Royal Protector", @"neck name is wrong");
STAssertEqualObjects(_testGuy.legsItem.name,@"Bloodhoof Legguards", @"neck name is wrong");
STAssertEqualObjects(_testGuy.feetItem.name,@"Treads of the Past", @"neck name is wrong");
STAssertEqualObjects(_testGuy.mainHandItem.name,@"Axe of the Tauren Chieftains", @"neck name is wrong");
STAssertEqualObjects(_testGuy.offHandItem.name,nil, @"offhand should be nil");
STAssertEqualObjects(_testGuy.trinketItem1.name,@"Rosary of Light", @"neck name is wrong");
STAssertEqualObjects(_testGuy.trinketItem2.name,@"Bone-Link Fetish", @"neck name is wrong");
STAssertEqualObjects(_testGuy.rangedItem.name,@"Ironfeather Longbow", @"neck name is wrong");
}
You need to make sure that the properties correctly match up with the JSON data that you loaded initially. Just a few comments on what’s going on here:
- This tests the information shown in the main screen Character cell.
- This tests the information shown in the CharacterDetailViewController when you click on a Character cell from the main screen.
To have a little fun, “forget on purpose” to run Product\Test (⌘-U), and commit and push your changes. You will see what your little friend Jenkins will find, when you update your Jenkins job script to include the tests.