单元测试模型继承自NSManagedObject的类

时间:2022-05-20 07:00:41

So...I'm trying to get unit tests set up in my iPhone App but I'm having some issues. I'm trying to test my model classes but they inherit directly from NSManagedObject. I'm sure this is a problem but I don't know how to get around it.

所以......我正试图在我的iPhone应用程序中设置单元测试,但我遇到了一些问题。我正在尝试测试我的模型类,但它们直接从NSManagedObject继承。我确定这是一个问题,但我不知道如何绕过它。

Everything is building and running as expected but I get this error when calling any method on the class I'm testing:

一切都按预期构建和运行,但是在调用我正在测试的类上的任何方法时出现此错误:

Unknown.m:0:0 unrecognized selector sent to instance 0xc2b120

Unknown.m:0:0无法识别的选择器发送到实例0xc2b120

If I follow this structure to create my object in my tests I end up with another error entirely but it still doesn't help me.

如果我按照这个结构在我的测试中创建我的对象,我最终会得到另一个错误,但它仍然无法帮助我。

If I instantiate my model like this:

如果我像这样实例化我的模型:

entry = [[TimeEntry alloc]
        initWithEntity:nil
        insertIntoManagedObjectContext:nil];

Then I end up with this error at runtime:

然后我在运行时结束这个错误:

An NSManagedObject of class 'TimeEntry' must have a valid NSEntityDescription.

类'TimeEntry'的NSManagedObject必须具有有效的NSEntityDescription。

If I try it like this:

如果我这样试试:

entry = [[TimeEntry alloc] init];

Then I end up with this error:

然后我最终得到了这个错误:

unrecognized selector sent to instance 0xc2b120

无法识别的选择器发送到实例0xc2b120

And if I follow the pattern laid out here:

如果我按照这里列出的模式:

model = [[NSManagedObjectModel mergedModelFromBundles: nil] retain];
NSLog(@"model: %@", model);
coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: model];
store = [coord addPersistentStoreWithType: NSInMemoryStoreType
                            configuration: nil
                                      URL: nil
                                  options: nil 
                                    error: NULL];
ctx = [[NSManagedObjectContext alloc] init];
[ctx setPersistentStoreCoordinator: coord];

entry = (TimeEntry *)[NSEntityDescription insertNewObjectForEntityForName:@"TimeEntry" inManagedObjectContext:ctx];

Then I get this error:

然后我收到这个错误:

could not locate an entity named 'TimeEntry' in this model.

无法在此模型中找到名为“TimeEntry”的实体。

Basically my question is this: how can I test a class that inherits from NSManagedObject?

基本上我的问题是:我如何测试从NSManagedObject继承的类?

3 个解决方案

#1


7  

In order to instantiate an NSManagedObject, you need an entity. Thus what you first tried — either passing nil for the entity or using bare -init (which isn't supported on NSManagedObject) — didn't work. You're doing the right thing by using -[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:] to create the object, you just need to:

为了实例化NSManagedObject,您需要一个实体。因此,你第一次尝试 - 为实体传递nil或使用bare -init(NSManagedObject不支持) - 都不起作用。通过使用 - [NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:]创建对象,您正在做正确的事情,您只需:

  1. Make sure that the entity TimeEntry exists in your data model.
  2. 确保数据模型中存在实体TimeEntry。
  3. Make sure that the entity TimeEntry is associated with the class TimeEntry in your data model.
  4. 确保实体TimeEntry与数据模型中的类TimeEntry相关联。
  5. Make sure that your data model is actually loaded by your tests.
  6. 确保您的测试实际加载了您的数据模型。

Note that unless you specifically want to test save/delete validation, you generally won't need to add a persistent store to your coordinator. (And if you're using an SQLite persistent store in your app, I'd strongly suggest using one in your tests too; the different persistent store types have different performance characteristics and supported queries.)

请注意,除非您特别想要测试保存/删除验证,否则通常不需要向协调器添加持久性存储。 (如果您在应用程序中使用SQLite持久性存储,我强烈建议您在测试中使用一个;不同的持久性存储类型具有不同的性能特征和支持的查询。)

For ensuring your data model is loaded, you'll find it much more fruitful I think to actually specify the URL to load it from, instead of just hoping that you've put it in the right place and that -mergedModelFromBundles: will do the right thing. I'd make it a member of your unit test bundle target, so it's compiled into your unit test bundle's Resources. That way you can just use an appropriate NSBundle method to get the path or URL to it.

为了确保你的数据模型被加载,你会发现实际上指定加载它的URL更有成效,而不是仅仅希望你把它放在正确的位置并且-mergedModelFromBundles:将执行正确的事。我将它作为单元测试包目标的成员,因此它被编译到您的单元测试包的资源中。这样你就可以使用适当的NSBundle方法来获取它的路径或URL。

Finally, you're going to want to put the set-up of your Core Data persistence stack — the model, persistent store coordinator, and a scratch context — in a -setUp method in your test case. Or in a -setUp method of a test case base class, if you want to create more than one test case class. (The same goes for tear-down of the persistence stack and -tearDown methods, of course.)

最后,您将要在测试用例的-setUp方法中设置Core Data持久性堆栈(模型,持久性存储协调器和临时上下文)的设置。或者在测试用例基类的-setUp方法中,如果要创建多个测试用例类。 (当然,对于持久性堆栈和-tearDown方法的拆除也是如此。)

#2


4  

I created a Sample for a Core Data Test environment on github http://github.com/mbrugger/CoreDataDependentProperties/blob/master/LPAutomatedObserving/Tests/ManagedObjectSenTestCase.m

我在github上为核心数据测试环境创建了一个示例http://github.com/mbrugger/CoreDataDependentProperties/blob/master/LPAutomatedObserving/Tests/ManagedObjectSenTestCase.m

Inherit your testcases from ManagedObjectSenTestCase.m/h and adjust the following two lines with your test target bundle identifier and data model name

从ManagedObjectSenTestCase.m / h继承您的测试用例,并使用您的测试目标包标识符和数据模型名称调整以下两行

        NSBundle* bundle = [NSBundle bundleWithIdentifier:@"com.yourcompany.ModelTest"];

    NSString* path = [bundle pathForResource:@"DataModel" ofType:@"mom"];

Code samples:

代码示例:

-(void) setUp
{
    pool = [[NSAutoreleasePool alloc] init];

    NSMutableSet *allBundles = [[[NSMutableSet alloc] init] autorelease];
    [allBundles addObjectsFromArray:[NSBundle allBundles]];

    NSBundle* bundle = [NSBundle bundleWithIdentifier:@"com.yourcompany.ModelTest"];

    NSString* path = [bundle pathForResource:@"DataModel"
                                                                                            ofType:@"mom"];

    NSURL* modelURL = [NSURL URLWithString:path];
    self.model = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] autorelease];

    self.coordinator = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model] autorelease];


    LPManagedObjectContext* tempContext = [[[NSManagedObjectContext alloc] init] autorelease];


    [tempContext setPersistentStoreCoordinator:coordinator];
    [tempContext setRetainsRegisteredObjects:YES];

    self.context = tempContext;
}

    -(void) tearDown
{
    NSLog(@"BEGIN: ManagedObjectSenTestCase tearDown");
    @try
    {
        self.context= nil;
        self.model = nil;
        self.coordinator = nil;
        [pool release];
        pool = nil;
    }
    @catch (NSException * e)
    {
        NSLog(@"%@",e);
        NSLog(@"%@", [e callStackSymbols]);
        NSLog(@"context reset failed!");
        @throw(e);

    }
    NSLog(@"END: ManagedObjectSenTestCase tearDown");
}

This sample creates the core data stack and you can insert entities into the created context for testing.

此示例创建核心数据堆栈,您可以将实体插入到创建的上下文中以进行测试。

#3


0  

I had the same problem. I eventually figured that it was unable to retrieve my model, but being a newbie to iPhone development I couldn't work out how to load it from a URL as per Chris' suggestion.

我有同样的问题。我最终认为它无法检索我的模型,但作为iPhone开发的新手,我根据Chris的建议无法解决如何从URL加载它。

Loading it from the bundle that the tests were running from is what worked for me:

从运行测试的包中加载它对我有用:

@implementation WhenWorkingWithATiming

Timing *timing;

NSManagedObjectModel *model;
NSPersistentStoreCoordinator *coordinator;
NSManagedObjectContext *context;


- (void) setUp {
    NSArray *bundles = [NSArray arrayWithObject:[NSBundle bundleForClass:[self class]]];
    model = [[NSManagedObjectModel mergedModelFromBundles:bundles] retain];
    NSLog(@"Model: %@", model);

    coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    context = [[NSManagedObjectContext alloc] init];
    [context setPersistentStoreCoordinator:coordinator];

    timing = (Timing *)[NSEntityDescription insertNewObjectForEntityForName:@"Timing" inManagedObjectContext:context];
}

- (void) tearDown {
    [context rollback];
    [context release];
    [coordinator release];
    [model release];
}

- (void) testThatTimingIsInitialised {
    STAssertNotNil(timing, @"should have a timing");
}

@end

#1


7  

In order to instantiate an NSManagedObject, you need an entity. Thus what you first tried — either passing nil for the entity or using bare -init (which isn't supported on NSManagedObject) — didn't work. You're doing the right thing by using -[NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:] to create the object, you just need to:

为了实例化NSManagedObject,您需要一个实体。因此,你第一次尝试 - 为实体传递nil或使用bare -init(NSManagedObject不支持) - 都不起作用。通过使用 - [NSEntityDescription insertNewObjectForEntityForName:inManagedObjectContext:]创建对象,您正在做正确的事情,您只需:

  1. Make sure that the entity TimeEntry exists in your data model.
  2. 确保数据模型中存在实体TimeEntry。
  3. Make sure that the entity TimeEntry is associated with the class TimeEntry in your data model.
  4. 确保实体TimeEntry与数据模型中的类TimeEntry相关联。
  5. Make sure that your data model is actually loaded by your tests.
  6. 确保您的测试实际加载了您的数据模型。

Note that unless you specifically want to test save/delete validation, you generally won't need to add a persistent store to your coordinator. (And if you're using an SQLite persistent store in your app, I'd strongly suggest using one in your tests too; the different persistent store types have different performance characteristics and supported queries.)

请注意,除非您特别想要测试保存/删除验证,否则通常不需要向协调器添加持久性存储。 (如果您在应用程序中使用SQLite持久性存储,我强烈建议您在测试中使用一个;不同的持久性存储类型具有不同的性能特征和支持的查询。)

For ensuring your data model is loaded, you'll find it much more fruitful I think to actually specify the URL to load it from, instead of just hoping that you've put it in the right place and that -mergedModelFromBundles: will do the right thing. I'd make it a member of your unit test bundle target, so it's compiled into your unit test bundle's Resources. That way you can just use an appropriate NSBundle method to get the path or URL to it.

为了确保你的数据模型被加载,你会发现实际上指定加载它的URL更有成效,而不是仅仅希望你把它放在正确的位置并且-mergedModelFromBundles:将执行正确的事。我将它作为单元测试包目标的成员,因此它被编译到您的单元测试包的资源中。这样你就可以使用适当的NSBundle方法来获取它的路径或URL。

Finally, you're going to want to put the set-up of your Core Data persistence stack — the model, persistent store coordinator, and a scratch context — in a -setUp method in your test case. Or in a -setUp method of a test case base class, if you want to create more than one test case class. (The same goes for tear-down of the persistence stack and -tearDown methods, of course.)

最后,您将要在测试用例的-setUp方法中设置Core Data持久性堆栈(模型,持久性存储协调器和临时上下文)的设置。或者在测试用例基类的-setUp方法中,如果要创建多个测试用例类。 (当然,对于持久性堆栈和-tearDown方法的拆除也是如此。)

#2


4  

I created a Sample for a Core Data Test environment on github http://github.com/mbrugger/CoreDataDependentProperties/blob/master/LPAutomatedObserving/Tests/ManagedObjectSenTestCase.m

我在github上为核心数据测试环境创建了一个示例http://github.com/mbrugger/CoreDataDependentProperties/blob/master/LPAutomatedObserving/Tests/ManagedObjectSenTestCase.m

Inherit your testcases from ManagedObjectSenTestCase.m/h and adjust the following two lines with your test target bundle identifier and data model name

从ManagedObjectSenTestCase.m / h继承您的测试用例,并使用您的测试目标包标识符和数据模型名称调整以下两行

        NSBundle* bundle = [NSBundle bundleWithIdentifier:@"com.yourcompany.ModelTest"];

    NSString* path = [bundle pathForResource:@"DataModel" ofType:@"mom"];

Code samples:

代码示例:

-(void) setUp
{
    pool = [[NSAutoreleasePool alloc] init];

    NSMutableSet *allBundles = [[[NSMutableSet alloc] init] autorelease];
    [allBundles addObjectsFromArray:[NSBundle allBundles]];

    NSBundle* bundle = [NSBundle bundleWithIdentifier:@"com.yourcompany.ModelTest"];

    NSString* path = [bundle pathForResource:@"DataModel"
                                                                                            ofType:@"mom"];

    NSURL* modelURL = [NSURL URLWithString:path];
    self.model = [[[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL] autorelease];

    self.coordinator = [[[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.model] autorelease];


    LPManagedObjectContext* tempContext = [[[NSManagedObjectContext alloc] init] autorelease];


    [tempContext setPersistentStoreCoordinator:coordinator];
    [tempContext setRetainsRegisteredObjects:YES];

    self.context = tempContext;
}

    -(void) tearDown
{
    NSLog(@"BEGIN: ManagedObjectSenTestCase tearDown");
    @try
    {
        self.context= nil;
        self.model = nil;
        self.coordinator = nil;
        [pool release];
        pool = nil;
    }
    @catch (NSException * e)
    {
        NSLog(@"%@",e);
        NSLog(@"%@", [e callStackSymbols]);
        NSLog(@"context reset failed!");
        @throw(e);

    }
    NSLog(@"END: ManagedObjectSenTestCase tearDown");
}

This sample creates the core data stack and you can insert entities into the created context for testing.

此示例创建核心数据堆栈,您可以将实体插入到创建的上下文中以进行测试。

#3


0  

I had the same problem. I eventually figured that it was unable to retrieve my model, but being a newbie to iPhone development I couldn't work out how to load it from a URL as per Chris' suggestion.

我有同样的问题。我最终认为它无法检索我的模型,但作为iPhone开发的新手,我根据Chris的建议无法解决如何从URL加载它。

Loading it from the bundle that the tests were running from is what worked for me:

从运行测试的包中加载它对我有用:

@implementation WhenWorkingWithATiming

Timing *timing;

NSManagedObjectModel *model;
NSPersistentStoreCoordinator *coordinator;
NSManagedObjectContext *context;


- (void) setUp {
    NSArray *bundles = [NSArray arrayWithObject:[NSBundle bundleForClass:[self class]]];
    model = [[NSManagedObjectModel mergedModelFromBundles:bundles] retain];
    NSLog(@"Model: %@", model);

    coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
    context = [[NSManagedObjectContext alloc] init];
    [context setPersistentStoreCoordinator:coordinator];

    timing = (Timing *)[NSEntityDescription insertNewObjectForEntityForName:@"Timing" inManagedObjectContext:context];
}

- (void) tearDown {
    [context rollback];
    [context release];
    [coordinator release];
    [model release];
}

- (void) testThatTimingIsInitialised {
    STAssertNotNil(timing, @"should have a timing");
}

@end