Reputation: 80265
I want to unit test my Core Data app (stress test with many records). Everything is set up for unit and application testing and working fine.
I would like to create many core data objects and then see if my graphing view controller still works. How would I do that?
If I create a test method in my MyAppApplicationTest.m
test class the test will just terminate the app after the test and I have no way to interact with the graphing view controller.
Am I stuck with having to create the many records in my AppDelegate
and delete that code later? Or is there a way to use the unit testing framework?
Thanks for your help.
Upvotes: 3
Views: 422
Reputation: 28409
There are several options for UI testing. However, in this case, I would suggest building a huge database, and keeping it around for various testing. You can optionally use it by setting a value on the command line, in the environment, or just in user defaults.
Come sample code to check user defaults, then environment for a setting...
static NSString * findOption(NSString *name) {
NSString *result = nil;
NSDictionary *options = [NSUserDefaults standardUserDefaults];
if ((result = [options objectForKey:name]) != nil) return result;
options = [[NSProcessInfo processInfo] environment];
if ((result = [options objectForKey:name]) != nil) return result;
return nil;
}
Note, if you just want to check command line parameters, instead of all domains of user defaults, you can use this...
NSDictionary *options = [[NSUserDefaults standardUserDefaults] volatileDomainForName:NSArgumentDomain];
Then, in your code that creates your persistent store, you can just see if the option is set...
if ((value = findOption(@"MundiLargeData")) && value.boolValue) {
// Create the persistent store with the pre-generated big database
// If creation failed, can continue with normal database as failsafe
}
Also, note that if you use SenTest for testing, it uses a command line parameter:
NSString *value = findOption(@"SenTest");
if (value) {
NSLog(@"Using SenTest: %@", value);
}
You can leave the code in, or #ifdef it out. It's pretty safe to just leave in there.
EDIT
Apologies -- I was going to add this immediately, but got called away...
Sorry about that. I never meant to imply that you ship your testing code. You certainly do not want to do that. I thought you were just looking for a way to load the big database when you were running the application so you could do manual UI testing on the device without having to compile a different version.
If you want to do something like that, then you have lots of options. You can write your tests as a category on the class that you want to test, and just exclude that file from release builds. If you give your tests a consistent naming scheme, like prefix with "test" or "runtimeTest" then you could have a method like this...
- (void)runAllMethodsThatBeginWith:(NSString*)prefix {
Class aClass = [self class];
Method *methods;
unsigned methodCount;
if ((methods = class_copyMethodList(aClass, &methodCount)))
{
// For this example, we only want methods that take no arguments and return void
char const *desiredEncoding = method_getTypeEncoding(class_getClassMethod([NSObject class], @selector(load)));
for (unsigned i = 0; i < methodCount; ++i) {
SEL selector = method_getName(methods[i]);
NSString *name = NSStringFromSelector(selector);
char const * typeEncoding = method_getTypeEncoding(methods[i]);
NSLog(@"%@: %s %s", name, typeEncoding, desiredEncoding);
NSRange range = [name rangeOfString:prefix];
if (range.location == 0 && range.length == prefix.length && strcmp(desiredEncoding, typeEncoding) == 0) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[self performSelector:selector];
#pragma clang diagnostic pop
}
}
// Don't forget to free the allocated methods array
free(methods);
}
}
It will find al the methods in your class that start with some name and return void and take no arguments. You can do other argument handling, but you will then have to deal with ARC related issues (since the compiler does not know for sure what to do -- it will at least give you a warning). Anyway, that's just to get you started... you could add type encoding as a parameter, and make it more generic...
Now, in your runtime code, you can just call...
[self runAllMethodsThatBeginWith:@"runtimeTest"];
It will run all methods that look like...
- (void)runtimeTestFoo {
}
If there are none, then it will just silently do nothing.
You can either exclude entire files with these implementations from the release build, or just exclude them with a macro ifdef.
Now none of your tests are compiled into the release, but they are for other stuff, and you can just call your tests anytime you want. If you know a specific test, then you can, of course, just use respondsToSelector: and conditionally run that specific test method.
EDIT
Hmmm. I thought you were looking for some way to dynamically decide what to do. If that's all you want, then just provide a subclass of the AppDelegate that creates the database...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Create your mondo database
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
Now, you have several options. In main.o, you tell it which app delegate class to use. You could use an option (#ifdef DEBUG), or an environment variable, or some other means to tell it which app delegate class to use...
#import "AppDelegate.h"
#define APP_DELEGATE AppDelegate
#ifdef USE_MY_SPECIAL_RUNTIME_TEST_DELEGATE
#import "RuntimeTestDelegate.h"
#undef APP_DELEGATE
#define APP_DELEGATE RuntimeTestDelegate
#endif
int main(int argc, char *argv[])
{
@autoreleasepool
{
return UIApplicationMain(argc, argv, nil, NSStringFromClass([APP_DELEGATE class]));
}
}
Or, it could just call NSClassFromString(@"MyTestingAppDelegate") to see if it is linked in to call it...
Or, if you want complete separation, simply create another target. Have the app delegate subclass in there, and use it in the main.m for that target. Link against all the other files.
Now you have a completely separate executable, that is identical to the "production" except it has a special app delegate that builds the database before launching the app.
Testing is hard. You have to know exactly what you want and do not want. There is no right answer that encompasses all situations.
There are MANY other options to this as well, like providing a config file in the resource bundle, including extra stuff in the app plist, providing a "guru" mode to the app where you can send it special commands during execution (like having it open a socket and reading special commands and sending back responses -- that way you can script scenarios for whatever you want, run them on your mac and remotely control the app -- there are tools for this as well).
Hopefully, one of these methods will fit what you are looking for.
Upvotes: 1