This post will talk briefly about setting up a bare-bones iOS app to illustrate what unit testing is all about and how to utilize the XCTest Framework for writing your own tests.
Imagine that you are writing a method to validate US and Canadian ZIP codes (or postal codes, as they’re called in Canada). In the US, ZIP codes are 5-digits long. Things like 77335, 06226, 29631 and so on.
In Canada, postal codes are typically written using 6 characters, with a space after the first three characters, like so:
LNL NLN
In the above code, L is a letter from the alphabet and N is a number or digit from 0-9. So Canadian postal codes can look like A2K 4L8, K7G 5T9, M2N 6P8 and so on. To make things more interesting, there are some additional rules about what letters are allowed (or rather not allowed) at various positions:
- The first letter cannot be W or Z.
- The letters D F I O Q U are not allowed at any location.
There might be more rules, but we’ll stop here. You get the idea where this is going. We’ll need to write a method that receives a user input string and then parses the string to make sure all these rules are met (for both US and Canada). Let’s assume you cook up the following class method somewhere in your model:
+ (BOOL) validateZip:(NSString *)userZip { . . . }
If the user enters the correct ZIP code, this method should return YES, otherwise it should return NO.
Once you finish developing this method, you simply go to the test section of your project and start adding in various test cases to check your method. There are some who maintain that these test cases should be written even before you create the above method – we will not argue about what approach is best – do whatever works for you.
For simplicity, this post only talks about the following testing methods, which suffice for our purpose here:
- XCTAssertTrue( something_that_should_be_true, @”optional message displayed when that something is false” )
- XCTAssertFalse (something_that_should_be_false, @”optional message displayed when that something is true” )
This is how you use them in the code (all you need is to include the header file of your model class in the test case file):
#import <UIKit/UIKit.h> #import <XCTest/XCTest.h> #import "ZipCodeValidation.h" @interface ZipCheckTests : XCTestCase @end @implementation ZipCheckTests - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. } - (void)tearDown { // Put teardown code here. This method is called after the invocation of each test method in the class. [super tearDown]; } // Invalid ZIP or Canadian Postal codes - (void)test_empty_zip_not_allowed { XCTAssertFalse([ZipCodeValidation validateZip:@""], @"blank entry is invalid"); } - (void)test_just_spaces_not_allowed { XCTAssertFalse([ZipCodeValidation validateZip:@" "], @"lots of spaces is not a valid entry"); } - (void)test_random_names_not_allowed { XCTAssertFalse([ZipCodeValidation validateZip:@"Ludwig Van Beethoven"], @"random names should fail"); } - (void)test_strange_characters_not_allowed { XCTAssertFalse([ZipCodeValidation validateZip:@"$[]&@!"], @"special characters are not allowed"); } // USA - (void)test_USA_01 { XCTAssertTrue([ZipCodeValidation validateZip:@"23324"], @"23324 should be valid"); } - (void)test_USA_02 { XCTAssertTrue([ZipCodeValidation validateZip:@" 06 226 "], @"spaces should not matter"); } - (void)test_USA_03 { XCTAssertTrue([ZipCodeValidation validateZip:@"12345"], @"any 5-digit code is valid for the US"); } - (void)test_USA_04 { XCTAssertFalse([ZipCodeValidation validateZip:@"1234"], @"4 digits not allowed"); } - (void)test_USA_05 { XCTAssertFalse([ZipCodeValidation validateZip:@"123456"], @"6 digits not allowed"); } - (void)test_USA_06 { XCTAssertFalse([ZipCodeValidation validateZip:@"12w45"], @"letters not allowed for the US"); } // Canada - (void)test_CANADA_01 { XCTAssertTrue([ZipCodeValidation validateZip:@"a2a 2a2"], @"a2a 2a2 should be fine"); } - (void)test_CANADA_02 { XCTAssertTrue([ZipCodeValidation validateZip:@"a2a2a2"], @"a2a2a2 should also work"); } - (void)test_CANADA_03 { XCTAssertTrue([ZipCodeValidation validateZip:@"A2A 2A2"], @"A2A 2A2 should be valid"); } - (void)test_CANADA_04 { XCTAssertFalse([ZipCodeValidation validateZip:@"W2A 2A2"], @"W cannot be the first letter"); } - (void)test_CANADA_05 { XCTAssertFalse([ZipCodeValidation validateZip:@"Z2A 2A2"], @"Z cannot be the first letter"); } - (void)test_CANADA_06 { XCTAssertTrue([ZipCodeValidation validateZip:@"S2W 2Z2"], @"W and Z elsewhere is fine"); } - (void)test_CANADA_07 { XCTAssertFalse([ZipCodeValidation validateZip:@"S2D 2Z2"], @"D is not allowed"); } - (void)test_CANADA_08 { XCTAssertFalse([ZipCodeValidation validateZip:@"S2F 2Z2"], @"F is not allowed"); } - (void)test_CANADA_09 { XCTAssertFalse([ZipCodeValidation validateZip:@"I2A 2Z2"], @"I is not allowed"); } - (void)test_CANADA_10 { XCTAssertFalse([ZipCodeValidation validateZip:@"B2A 2O2"], @"O is not allowed"); } - (void)test_CANADA_11 { XCTAssertFalse([ZipCodeValidation validateZip:@"B2Q 2A2"], @"Q is not allowed"); } - (void)test_CANADA_12 { XCTAssertFalse([ZipCodeValidation validateZip:@"U2A 2A5"], @"U is not allowed"); } @end
You can add as many tests you like in this file. To run these tests, use
Product —> Test (or the keyboard shortcut Command + U)
Xcode will run all your tests and place cute little check signs next to each test to let you know which tests pass and which tests fail.
That’s it! Isn’t unit testing cool? Now that I’ve wrapped my head around the basics, I will start adding unit tests to several of my iOS projects.