update:. I added example code about KIF 2.0 at the bottom after the separator. for those who are more interested in KIF than the specific problem I am facing:
After some research and experimentation. I narrowed down my options to two test automation libraries: Frank and KIF . In the end, I decided to use KIF when borrowing the Gherkin cucumber syntax to describe my unit tests.
The reason I chose KIF (not Frank ) was because KIF based on 100% obj-c, instead of using ruby, as well as in the case of Frank . Thus, the setup is simpler, and it is more applicable to my narrow exam requirement. In doing so, I admit that Frank would be more useful if my application were more complex (i.e. using intput from multiple servers, etc.). You can see the last quarter of this great presentation to learn more about the pros and cons of KIF, Frank, and other test automation systems, including Apple Automation UI .
After using KIF, I found the error causing the error above, and I was able to reproduce it using KIF 100% of the time! The reason this happened so rarely was because it only happened when I quickly shot through the screens. And since KIF automates the steps, they do it at an incredibly fast speed .. which detected an error :).
So the following example will be an example of the code I used for testing. This is just to give you a quick idea of what KIF (and Gherkin) can do for you:
in one file, I specify the scripts that I want to run:
- (void)initializeScenarios; { [self addScenario:[KIFTestScenario scenarioToCompleteSignInAndLoadInbox]]; [self addScenario:[KIFTestScenario scenarioToFillAttachmentsWithData]]; [self addScenario:[KIFTestScenario scenarioToViewAndLoadFileBucket]]; [self addScenario:[KIFTestScenario scenarioToViewAndLoadFileBucketSubView]]; }
each script displays steps (to learn more about outline syntax - and behavioral development based on test driver development, I highly recommend reading this excellent book on cucumber ):
+ (id)scenarioToCompleteSignInAndLoadInbox { KIFTestScenario *scenario = [KIFTestScenario scenarioWithDescription:@"Test that a user can successfully log in."]; [scenario addStepsFromArray:[KIFTestStep stepsCompleteSignInAndLoadInbox]]; return scenario; } + (id)scenarioToFillAttachmentsWithData { KIFTestScenario* scenario = [KIFTestScenario scenarioWithDescription:@"Test that we can view the attachments folder and fill it with data."]; [scenario addStepsFromArray:[KIFTestStep stepsToFillAttachmentsWithData]]; return scenario; } + (id)scenarioToViewAndLoadFileBucket { KIFTestScenario *scenario = [KIFTestScenario scenarioWithDescription:@"Test that a user can successfully view and load file bucket parent view"]; [scenario addStepsFromArray:[KIFTestStep stepsToViewAndLoadFileBucketPage]]; return scenario; } + (id)scenarioToViewAndLoadFileBucketSubView { KIFTestScenario *scenario = [KIFTestScenario scenarioWithDescription:@"Test that a user can successfully view and load filet bucket sub view"]; [scenario addStepsFromArray:[KIFTestStep stepsToViewAndLoadFileBucketSubPage]]; return scenario; }
and steps are defined using KIF UI automation methods (this is just one example):
// this step assumes there is an attachment folder that contains emails with attachments + (NSArray *)stepsToFillAttachmentsWithData { NSMutableArray* steps = [@[] mutableCopy]; [steps addObject: [KIFTestStep stepToTapViewWithAccessibilityLabel:@"InboxMenuButton"]]; NSIndexPath* indexPath = [NSIndexPath indexPathForRow:remoteAttachmentFolderNumber inSection:0]; KIFTestStep* tapAttachmentRowStep = [KIFTestStep stepToTapRowInTableViewWithAccessibilityLabel: @"attachments" atIndexPath:indexPath]; [steps addObject:[KIFTestStep stepToWaitForNotificationName: (NSString *)kBeganSyncingOlderEmails object:nil whileExecutingStep:tapAttachmentRowStep]]; [steps addObject:tapAttachmentRowStep]; [steps addObject: [KIFTestStep stepToWaitForViewWithAccessibilityLabel:@"attachments"]]; KIFTestStep *fillingInboxStep = [KIFTestStep stepToWaitForNotificationName: (NSString *)kOldMailBatchDelivered object:nil]; [fillingInboxStep setTimeout:kSpecialTimeoutForLongTests]; [steps addObject:fillingInboxStep]; return steps; }
KIF 2.0 code example: KIF 2.0 uses Xcode 5 an all-new test navigator .. which is a big improvement than what KIF 1.0 did .. now your tests feel much more organic and natural than the past .. (that is, it goes in real time, and does not create scripts that run in the future, etc.). You can even check each of them using the play button, etc. you should try.
Here are some examples (again, using gherkin syntax):
here is the implementation of some test cases in category files:
- (void)reset { [self runBlock:^KIFTestStepResult(NSError **error) { BOOL successfulReset = YES; // Do the actual reset for your app. Set successfulReset = NO if it fails. AppDelegate* appDelegate = [[UIApplication sharedApplication] delegate]; [appDelegate resetApp]; KIFTestCondition(successfulReset, error, @"Failed to reset some part of the application."); return KIFTestStepResultSuccess; }]; } - (void)flushDbase { [self runBlock:^KIFTestStepResult(NSError **error){ NSURL *url = [NSURL URLWithString:@"http://randomdomain.com/flush_db"]; NSURLRequest *request = [NSURLRequest requestWithURL:url]; NSError *connectionError = nil; BOOL databaseFlushSucceeded = YES; NSURLResponse *response; NSData *resultData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&connectionError]; if (!resultData) { databaseFlushSucceeded = NO; KIFTestCondition(databaseFlushSucceeded, error, @"failed to connect to server!"); } if (connectionError) { databaseFlushSucceeded = NO; KIFTestCondition(databaseFlushSucceeded, error, [NSString stringWithFormat:@"connection failed. Error: %@", [connectionError localizedDescription]]); } return KIFTestStepResultSuccess; }]; } - (void)navigateToLoginPage { [self tapViewWithAccessibilityLabel:@"login email"]; } - (void)returnToLoggedOutHomeScreen { [self tapViewWithAccessibilityLabel:@"Logout"]; [self tapViewWithAccessibilityLabel:@"Logout"]; // Dismiss alert. }