使用 OCMock 和 Core Data 测试控制器方法

发布于 2024-08-21 13:45:27 字数 2149 浏览 8 评论 0原文

我刚刚掌握了 TDD 和模拟的概念,并且遇到了如何正确理解的问题。我有一个下拉表,允许用户创建一个新的核心数据对象并将其保存到数据存储中。我不确定我是否采取了最好的方法来测试它。

- (IBAction)add:(id)sender 
{  
  NSString *itemName = [self.itemNameTextField stringValue];
  SGItem *newItem = [NSEntityDescription insertNewObjectForEntityForName:kItemEntityName inManagedObjectContext:[self managedObjectContext]];
  newItem.name = itemName;

  NSError *error = nil;
  BOOL canSaveNewItem = [[self managedObjectContext] save:&error];
  if (!canSaveNewItem) 
  {
    [NSApp presentError:error]; 
  }

  [self clearFormFields];  // Private method that clears text fields, disables buttons
  [NSApp endSheet:[self window] returnCode:NSOKButton];
}

我正在尝试编写两种测试方法来测试这一点:一种测试托管对象无法保存的场景,另一种测试托管对象成功保存的情况。

@interface SGAddItemWindowControllerTests : SGTestCase 
{
@private
  SGAddItemWindowController *addItemWindowController;
  id mockApp;
  id mockNameField;
}

- (void)setUp 
{
  mockNameField = [OCMockObject mockForClass:[NSTextField class]];
  mockApp = [OCMockObject mockForClass:[NSApplication class]];

  addItemWindowController = [[BLAddItemWindowController alloc] init];  
  [addItemWindowController setValue:mockNameField forKey:@"itemNameTextField"];
}

- (void)testAddingNewItemFromSheetFailed
{
  // Setup
  NSString *fakeName = @"";
  [[[mockNameField expect] andReturn:fakeName] stringValue];
  [[mockApp expect] presentError:[OCMArg any]];

  // Execute
  [addItemWindowController add:nil];

  // Verify
  [mockApp verify];
}

- (void)testAddingNewItemFromSheetSucceeds
{
  // Setup
  NSString *fakeName = @"Item Name";
  [[[mockNameField expect] andReturn:fakeName] stringValue];
  [[mockApp expect] endSheet:[OCMArg any] returnCode:NSOKButton];

  // Execute
  [addItemWindowController add:nil];

  // Verify
  [mockApp verify];
  [mockNameField verify];
}

@end

以下是我知道我遇到的问题,但不确定如何解决:

  1. 我不确定如何在测试方面处理托管对象上下文。我应该调出整个核心数据堆栈还是只创建 NSManagedObjectContext 的模拟?
  2. 仅设置文本字段值作为触发 if 语句的方式的想法似乎是错误的。理想情况下,我认为我应该删除 save: 方法并返回 YES 或 NO,但鉴于问题 1,我不确定这一切的核心数据方面。

我认为我走在正确的轨道上,但我可以就如何解决我的问题使用第二种意见,并让我走上测试代码片段的正确道路。

I am just grasping the concepts of TDD and mocking, and am running into an issue in terms of how to properly. I have a sheet that drops down and lets a user create a new core data object and save it to the data store. I am not sure if I am taking the best approach to testing it.

- (IBAction)add:(id)sender 
{  
  NSString *itemName = [self.itemNameTextField stringValue];
  SGItem *newItem = [NSEntityDescription insertNewObjectForEntityForName:kItemEntityName inManagedObjectContext:[self managedObjectContext]];
  newItem.name = itemName;

  NSError *error = nil;
  BOOL canSaveNewItem = [[self managedObjectContext] save:&error];
  if (!canSaveNewItem) 
  {
    [NSApp presentError:error]; 
  }

  [self clearFormFields];  // Private method that clears text fields, disables buttons
  [NSApp endSheet:[self window] returnCode:NSOKButton];
}

I'm trying to write two test methods to test this: one that tests the scenario where the managed object can't save and one where it successfully saves.

@interface SGAddItemWindowControllerTests : SGTestCase 
{
@private
  SGAddItemWindowController *addItemWindowController;
  id mockApp;
  id mockNameField;
}

- (void)setUp 
{
  mockNameField = [OCMockObject mockForClass:[NSTextField class]];
  mockApp = [OCMockObject mockForClass:[NSApplication class]];

  addItemWindowController = [[BLAddItemWindowController alloc] init];  
  [addItemWindowController setValue:mockNameField forKey:@"itemNameTextField"];
}

- (void)testAddingNewItemFromSheetFailed
{
  // Setup
  NSString *fakeName = @"";
  [[[mockNameField expect] andReturn:fakeName] stringValue];
  [[mockApp expect] presentError:[OCMArg any]];

  // Execute
  [addItemWindowController add:nil];

  // Verify
  [mockApp verify];
}

- (void)testAddingNewItemFromSheetSucceeds
{
  // Setup
  NSString *fakeName = @"Item Name";
  [[[mockNameField expect] andReturn:fakeName] stringValue];
  [[mockApp expect] endSheet:[OCMArg any] returnCode:NSOKButton];

  // Execute
  [addItemWindowController add:nil];

  // Verify
  [mockApp verify];
  [mockNameField verify];
}

@end

Here are the issues I know I have, but am not sure how to work out:

  1. I am not sure how to handle dealing with the managed object context in terms of the test. Should I bring up the entire core data stack or just create a mock of NSManagedObjectContext?
  2. The idea of just setting the text field values as the way to trigger the if statement seems wrong. Ideally I think I should stub out the save: method and return YES or NO, but given question 1 I'm not sure about the Core Data aspects of it all.

I think I'm on the right track, but I could use a second opinion on how to tackle my issues and set me on the right path for testing the code snippet.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

清眉祭 2024-08-28 13:45:27

Justin,

我对问题 #1 所做的是创建一个实际的 NSManagedObjectContext 但创建一个内存持久性存储。没有任何东西到达磁盘,我测试了 CoreData 版本的真相。

我有一个 MWCoreDataTest 类(在我的情况下扩展为 GTMTestCase),它构建 moc 并初始化持久性存储

    - (NSManagedObjectContext *) managedObjectContext {

    if (managedObjectContext != nil) {
        return managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator: coordinator];
    }

    return managedObjectContext;
}



- (NSPersistentStoreCoordinator*)persistentStoreCoordinator;
{
    if (persistentStoreCoordinator) return persistentStoreCoordinator;
    NSError* error = nil;
    NSManagedObjectModel *mom = [self managedObjectModel];
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
                                  initWithManagedObjectModel:mom];


    if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
                                                  configuration:nil
                                                            URL:nil
                                                        options:nil
                                                          error:&error]) {
        [[NSApplication sharedApplication] presentError:error];
        return nil;
    }
    return persistentStoreCoordinator;
}

WRT #2,我认为没关系 - 如果您计划在类中测试多个行为,请将其移至

[addItemWindowController setValue:mockNameField forKey:@"itemNameTextField"];

testAdding..方法

如果您解决了#1,那么您只需将 itemNameText 字段设置为 nil 即可触发保存验证。

WRT #3,我将验证在 NSApp 上构建模拟 === 在 NSApplication 上构建模拟

Justin,

What I do for question #1 is to create an actual NSManagedObjectContext but create an im-memory persistence store. Nothing hits the disk and I test the CoreData version of the truth.

I have a MWCoreDataTest class (extends in my case GTMTestCase) that builds the moc and initializes the persistence store

    - (NSManagedObjectContext *) managedObjectContext {

    if (managedObjectContext != nil) {
        return managedObjectContext;
    }

    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        managedObjectContext = [[NSManagedObjectContext alloc] init];
        [managedObjectContext setPersistentStoreCoordinator: coordinator];
    }

    return managedObjectContext;
}



- (NSPersistentStoreCoordinator*)persistentStoreCoordinator;
{
    if (persistentStoreCoordinator) return persistentStoreCoordinator;
    NSError* error = nil;
    NSManagedObjectModel *mom = [self managedObjectModel];
    persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc]
                                  initWithManagedObjectModel:mom];


    if (![persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType
                                                  configuration:nil
                                                            URL:nil
                                                        options:nil
                                                          error:&error]) {
        [[NSApplication sharedApplication] presentError:error];
        return nil;
    }
    return persistentStoreCoordinator;
}

WRT #2, I think that's ok - if you plan on testing more than one behavior in the class, move the

[addItemWindowController setValue:mockNameField forKey:@"itemNameTextField"];

to the testAdding.. method

If you solve #1, then you could just set the itemNameText field to nil and your save validation would trigger.

WRT #3, I would validate that building a mock on NSApp === building a mock on NSApplication

小瓶盖 2024-08-28 13:45:27

你想测试什么?您想测试 Core Data 是否保存?或者,您想测试您的应用程序是否正确响应 CoreData 调用的结果?

无论哪种方式,我认为您应该提取一个按照以下方式执行保存的方法:

-(BOOL)saveNewItem:(NSString *)itemName error:(NSError **)error { 
    SGItem *newItem = [NSEntityDescription insertNewObjectForEntityForName:kItemEntityName inManagedObjectContext:[self managedObjectContext]];
  newItem.name = itemName;

  NSError *error = nil;
  return[[self managedObjectContext] save:&error];
}

- (IBAction)add:(id)sender {  
  NSString *itemName = [self.itemNameTextField stringValue];
  NSError *error = nil;
  BOOL canSaveNewItem = [self saveNewItem:itemName error:&error];
  if (!canSaveNewItem) {
    [NSApp presentError:error]; 
  }

  [self clearFormFields];  // Private method that clears text fields, disables buttons
  [NSApp endSheet:[self window] returnCode:NSOKButton];
}

这样您就可以通过设置内存存储来测试核心数据保存是否按预期工作,而不必关心业务逻辑。您还应该能够覆盖或模拟此方法的结果以测试业务逻辑。

我什至可能会将所有核心数据内容移至一个单独的类中,该类将封装交互以便于模拟。

What is that you want to test? Do you want to test that Core Data does the saving or not? Or, do you want to test that your application responds correctly to the result of the call to CoreData?

Either way I think you should extract a method that performs the saving along the lines of:

-(BOOL)saveNewItem:(NSString *)itemName error:(NSError **)error { 
    SGItem *newItem = [NSEntityDescription insertNewObjectForEntityForName:kItemEntityName inManagedObjectContext:[self managedObjectContext]];
  newItem.name = itemName;

  NSError *error = nil;
  return[[self managedObjectContext] save:&error];
}

- (IBAction)add:(id)sender {  
  NSString *itemName = [self.itemNameTextField stringValue];
  NSError *error = nil;
  BOOL canSaveNewItem = [self saveNewItem:itemName error:&error];
  if (!canSaveNewItem) {
    [NSApp presentError:error]; 
  }

  [self clearFormFields];  // Private method that clears text fields, disables buttons
  [NSApp endSheet:[self window] returnCode:NSOKButton];
}

This way you can test that Core Data saving works as expected by settings up an in memory store and not have to care about the business logic. You should also be able to override or mock the result of this method for testing the business logic.

I would perhaps even move all the Core Data stuff to a separate class that would encapsulate the interaction for easier mocking.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文