Тестирование метода контроллера с OCMock и Core Data
Я просто понимаю концепции 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
Вот проблемы, которые я знаю, у меня есть, но я не уверен, как решить:
- Я не уверен, как обращаться с контекстом управляемого объекта с точки зрения теста. Должен ли я поднять весь основной стек данных или просто создать макет
NSManagedObjectContext
? - Идея просто установить значения текстового поля как способ запуска оператора if кажется неправильной. В идеале я думаю, что я должен заглушить
save:
метод и вернуть ДА или НЕТ, но, учитывая вопрос 1, я не уверен насчет аспектов Core Data всего этого.
Я думаю, что я на правильном пути, но я мог бы использовать второе мнение о том, как решать свои проблемы, и указать правильный путь для тестирования фрагмента кода.
2 ответа
Джастин,
Что я делаю для вопроса № 1, так это для создания фактического NSManagedObjectContext, но для создания постоянного хранилища в памяти. Ничто не попадает на диск, и я проверяю версию истины CoreData.
У меня есть класс MWCoreDataTest (расширяется в моем случае GTMTestCase), который создает МОК и инициализирует хранилище постоянства
- (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
Что вы хотите проверить? Вы хотите проверить, что 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];
}
Таким образом, вы можете проверить, что сохранение основных данных работает должным образом, настроив хранилище в памяти и не заботясь о бизнес-логике. Вы также должны иметь возможность переопределить или смоделировать результат этого метода для тестирования бизнес-логики.
Возможно, я бы даже переместил весь материал Core Data в отдельный класс, который бы инкапсулировал взаимодействие для облегчения насмешек.