单元测试具有许多对象的核心数据

时间:2012-07-26 09:54:26

标签: iphone ios unit-testing core-data

我想对我的核心数据应用进行单元测试(压力测试有很多记录)。一切都是为单元和应用程序测试而设置的,并且工作正常。

我想创建许多核心数据对象,然后查看我的图形视图控制器是否仍然有效。我该怎么做?

如果我在MyAppApplicationTest.m测试类中创建测试方法,测试将在测试后终止应用,我无法与图形视图控制器进行交互。

我不得不在我的AppDelegate中创建许多记录并稍后删除该代码吗?或者有没有办法使用单元测试框架?

感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

UI测试有几种选择。但是,在这种情况下,我建议建立一个庞大的数据库,并保持它的各种测试。您可以选择通过在命令行,环境中或仅在用户默认值中设置值来使用它。

来样本代码检查用户默认值,然后环境设置...

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;
}

注意,如果您只想检查命令行参数,而不是所有用户默认域,您可以使用此...

NSDictionary *options  = [[NSUserDefaults standardUserDefaults] volatileDomainForName:NSArgumentDomain];

然后,在创建持久存储的代码中,您可以看到该选项是否已设置...

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
}

另请注意,如果使用SenTest进行测试,则使用命令行参数:

NSString *value = findOption(@"SenTest");
if (value) {
    NSLog(@"Using SenTest: %@", value);
}

您可以保留代码,或者#ifdef out。离开那里是非常安全的。

修改

抱歉 - 我打算立即添加它,但被叫走了......

很抱歉。我从不打算暗示您运送测试代码。你当然不想这样做。我认为您只是在运行应用程序时寻找加载大数据库的方法,这样您就可以在设备上进行手动UI测试,而无需编译不同的版本。

如果你想做那样的事情,那么你有很多选择。您可以将测试编写为要测试的类的类别,并将该文件从发布版本中排除。如果你给你的测试一个一致的命名方案,比如前缀为“test”或“runtimeTest”那么你可以有这样的方法......

- (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);
    }
}

它会在你的类中找到以某个名称开头并返回void并且不带参数的方法。你可以做其他的参数处理,但是你必须处理ARC相关的问题(因为编译器不知道该做什么 - 它至少会给你一个警告)。无论如何,这只是为了让你开始...你可以添加类型编码作为参数,并使其更通用...

现在,在您的运行时代码中,您只需调用...

[self runAllMethodsThatBeginWith:@"runtimeTest"];

它将运行所有看起来像......的方法。

- (void)runtimeTestFoo {
}

如果没有,那么它就会默默无闻。

您可以从发布版本中使用这些实现排除整个文件,也可以使用宏ifdef排除它们。

现在没有任何测试编译到发行版中,但它们用于其他内容,您可以随时调用您的测试。如果你知道一个特定的测试,那么你当然可以只使用respondsToSelector:并有条件地运行那个特定的测试方法。

修改

嗯。我以为你正在寻找一些动态决定做什么的方法。如果这就是你想要的,那么只需提供创建数据库的AppDelegate的子类......

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Create your mondo database
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

现在,您有几种选择。在main.o中,您可以告诉它使用哪个app delegate类。您可以使用选项(#ifdef DEBUG),或环境变量,或其他一些方法来告诉它使用哪个应用委托类...

#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]));
    }
}

或者,它可以调用NSClassFromString(@“MyTestingAppDelegate”)来查看它是否被链接以调用它...

或者,如果您想完全分离,只需创建另一个目标。让应用程序委托子类,并在main.m中为该目标使用它。链接所有其他文件。

现在你有一个完全独立的可执行文件,它与“生产”相同,只是它有一个特殊的应用程序委托,它在启动应用程序之前构建数据库。

测试很难。你必须确切地知道你想要什么,不想要什么。没有正确的答案涵盖所有情况。

此外还有很多其他选项,例如在资源包中提供配置文件,包括app plist中的额外内容,为应用程序提供“guru”模式,您可以在执行期间向其发送特殊命令(比如让它打开一个套接字并阅读特殊命令并发回回复 - 这样你就可以编写任何你想要的场景,在你的mac上运行它们并远程控制应用程序 - 这里也有工具。

希望其中一种方法符合您的要求。