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
}