XCTest и CoreData
Я пытаюсь использовать Unit test мою модель в Xcode 5 с помощью классов и методов XCTest.
Поскольку мои классы моделей наследуют managedObject
, я не могу просто создать экземпляр (alloc/init) и вызвать геттеры и сеттеры или методы, которые мне нужно проверить.
Мне нужно создать их с помощью NSEntityDescription
и использовать managedObjectContext
.
То есть с этим моментом я получаю проблемы.
Я не знаю, где и как создать managedObjectContext
для целей единичных тестов.
Если у кого-нибудь есть советы или примеры кода, это будет очень полезно.
Спасибо.
Ответы
Ответ 1
Я использую память в памяти для своих модульных тестов и создаю все объекты внутри этого.
Этот метод класса можно поместить в TestsHelper.m
+ (NSManagedObjectContext *)managedObjectContextForTests {
static NSManagedObjectModel *model = nil;
if (!model) {
model = [NSManagedObjectModel mergedModelFromBundles:[NSBundle allBundles]];
}
NSPersistentStoreCoordinator *psc = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:model];
NSPersistentStore *store = [psc addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:nil];
NSAssert(store, @"Should have a store by now");
NSManagedObjectContext *moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
moc.persistentStoreCoordinator = psc;
return moc;
}
Это работает для меня, потому что я использую Dependency Injection для передачи моего мока, а не с помощью синглета.
Ответ 2
Я согласен с @Abizern: передайте экземпляр NSManagedObjectContext
в свой код вместо того, чтобы полагаться на делегат приложения, глобальную переменную или пользовательский вспомогательный синглтон.
Инъекция зависимостей
Если вам известно, что какой-то контроллер нуждается в доступе, добавьте параметр NSManagedObjectContext
к его методу init и сохраните ссылку на него:
@interface SomeController : NSObject
@property (nonatomic, strong, readwrite) NSManagedObjectContext *context;
- (instancetype)initWithContext:(NSManagedObjectContext *)context;
@end
Это минимальное требование для "инъекции зависимостей". Вам не нужна фантазия, чтобы сделать инъекцию для вас. Вместо этого в приложении вы назначаете свой обычный экземпляр NSManagedObjectContext
, который, вероятно, использует хранилище SQLite. В ваших тестах вы создаете отдельный NSManagedObjectContext
с хранилищем в памяти и передаете его на SomeController
. Это даже работало бы с (частичным) издевательством с помощью OCMock.
Есть несколько хороших примеров на objc.io # 4, особенно пример приложения Chris Eidhof: http://www.objc.io/issue-4/full-core-data-application.html
Как получить доступ к MOC
Посмотрев на пример кода objc.io в GitHub, вы увидите PersistentStack
вспомогательный класс, который выполняет инициализацию контекста управляемого объекта для вашего приложения. Крис использует подклассу абстрактного теста, чтобы обеспечить контекст .
Общая рекомендация такова:
- Не полагайтесь на вспомогательный класс singleton на протяжении всего кода, потому что трудно заменить его контекст тестовым контекстом. Это особенность, которую я обнаружил и которая связана с тем, что XCTest "впрыскивается" в исполняемый код приложения. В Интернете были рабочие примеры, но они не делали то, что я ожидал, с Xcode 5.1 и XCTest.
- Вместо этого при необходимости подготовьте
NSManagedObjectContext
. Передайте управляемые объекты и используйте управляемые объекты, чтобы получить доступ к контексту.
Флориан Куглер заявляет об этом следующим образом:
Управляемые объекты должны быть переданы в приложении, пересекая по крайней мере барьер модели-контроллера и потенциально даже барьер контроллера. Последнее несколько более противоречиво и может быть лучше абстрагировано, например. определяя протокол, которому должен соответствовать объект, чтобы потреблять определенное представление, или путем реализации методов конфигурации в категории представления, которые перекрывают пробел от объекта модели до специфики представления.
В любом случае мы не должны ограничивать управляемые объекты на уровне модели и вытаскивать их данные в разные структуры, как только мы хотим их передать. Управляемые объекты являются первоклассными гражданами в приложении Core Data, и мы должны использовать их соответственно. Например, управляемые объекты должны передаваться между контроллерами представления, чтобы предоставить им нужные им данные.
Чтобы получить доступ к контексту управляемого объекта, мы часто видим такой код в виде контроллеров:
NSManagedObjectContext *context =
[(MyApplicationDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext];
Если вы уже передаете объект модели контроллеру представления, гораздо лучше получить доступ к контексту непосредственно через этот объект:
NSManagedObjectContext *context = self.myObject.managedObjectContext;
Это удаляет скрытую зависимость от делегата приложения и делает его более читаемым, а также легче тестировать.
Это лучший общий совет, который я получил. Теперь я могу протестировать использование Core Data, где мне нужно. Раньше доступ к контексту через глобальный одноэлементный класс (пользовательский класс или делегат приложения) был удобен в использовании, но в тестах трудно проверить.
Ответ 3
Swift
NSManagedObjectContext
расширение, создающее стек CoreData
в памяти
extension NSManagedObjectContext {
class func contextForTests() -> NSManagedObjectContext {
// Get the model
let model = NSManagedObjectModel.mergedModel(from: Bundle.allBundles)!
// Create and configure the coordinator
let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
try! coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
// Setup the context
let context = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
context.persistentStoreCoordinator = coordinator
return context
}
}
Затем используйте его в своих тестах:
class YourTests: XCTestCase {
private var context: NSManagedObjectContext?
override func setUp() {
self.context = NSManagedObjectContext.contextForTests()
}
// And use it in your tests
}