Swift не может тестировать данные ядра в тестах Xcode?

Я работаю над проектом iOS, который использует основные данные. Я использую быстро. Стек Core Data настроен правильно, и все кажется прекрасным. Я создал класс для объекта (NSManagedObject) под названием TestEntity. Класс выглядит следующим образом:

import UIKit
import CoreData

class TestEntity: NSManagedObject {
    @NSManaged var name: NSString
    @NSManaged var age: NSNumber
}

Итак, я пытаюсь вставить новый код TestEntity в код, используя эту строку кода:

let te: TestEntity = NSEntityDescription.insertNewObjectForEntityForName("TestEntity", inManagedObjectContext: ctx) as TestEntity

Затем я получаю эту ошибку:

enter image description here

Я видел несколько ответов на переполнение стека, которые говорят, что мне нужно беспокоиться о имени модуля. Итак, я посмотрел на документы: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WritingSwiftClassesWithObjective-CBehavior.html

Затем я попал в основной объект данных для TestEntity, и в поле класса я ввел myAppName.TestEntity

Когда я запустил приложение в этой строке:

let te: TestEntity = NSEntityDescription.insertNewObjectForEntityForName("TestEntity", inManagedObjectContext: ctx) as TestEntity

все равно дает мне ту же ошибку.

Что еще я могу делать неправильно?

EDIT: Итак, я смог сделать приложение не аварийным, изменив класс TestEntity NSManagedObject: импортировать UIKit import CoreData​​p >

@objc(TestEntity) class TestEntity: NSManagedObject {
    @NSManaged var name: NSString
    @NSManaged var age: NSNumber
}

Итак, я добавил в него @objc (TestEntity). Это работает с или без добавления имени приложения перед именем класса TestEntity в инспекторе модели данных основных данных.

Это работает, но когда я запускаю тесты, эта строка все еще вылетает:

let te: TestEntity = NSEntityDescription.insertNewObjectForEntityForName("TestEntity", inManagedObjectContext: ctx) as TestEntity

Итак, я обнаружил, что это проблема для других людей: Как получить доступ к классам Obj-C, генерированным Core Data, в тестовых целях?

Как мы можем получить основные данные для работы в тестах с быстрым. Я НЕ использую заголовок моста в целевой программе приложения, и все это отлично работает. Тестовая цель все еще падает.

Как я могу исправить тестовую цель, чтобы она могла запускать тесты данных ядра?

Ответы

Ответ 1

С Xcode 7 и @testable вам больше не нужно обновлять managedObjectClassName или использовать другие хаки. Вот что я сделал, чтобы заставить его работать в Xcode 7.2.

  • Задайте тестовое целевое приложение-хост и установите флажок "Разрешить тестирование API-интерфейсов хост-приложений".

введите описание изображения здесь

  1. Убедитесь, что none ваших обычных классов имеет целевое членство, указывающее на цель тестирования. Для целей тестирования следует установить только классы с кодом unit test.

введите описание изображения здесь

  1. Добавьте строку @testable в начало всех тестовых классов:
import XCTest
@testable import MyApp

class MyAppTests: XCTestCase {
}

Если у вас все еще есть проблемы, вы можете попробовать следующие дополнительные советы: https://forums.developer.apple.com/message/28773#28949

Я сражался с этим на какое-то время, поэтому надеюсь, что это поможет кому-то другому.

Ответ 2

Это потому, что Framework CoreData все еще находится в Objective-C. Swift использует классы с именами, поэтому для CoreData для поиска ваших быстрых классов вам нужно указать имя класса с этим пространством имен следующим образом:

enter image description here

Проблема, которую вы будете иметь, заключается в том, что ваше приложение не имеет того же пространства имен, что и при выполнении вами тестов. <AppName>.<ClassName> vs <AppName>Tests.<ClassName>

EDIT: решение для работы в качестве приложения и тестов

Я просто написал фрагмент кода для решения проблемы <AppName>.<ClassName> vs <AppName>Tests.<ClassName>. Решение, которое я использую в это время (Xcode 6.1), НЕ должно заполнить поле Class в пользовательском интерфейсе CoreData (см. Выше) и сделать это вместо кода.

Этот код обнаружит, что вы работаете как приложение vs Tests и используете правильное имя модуля и обновляете managedObjectClassName.

lazy var managedObjectModel: NSManagedObjectModel = {
    // The managed object model for the application. This property is not optional...
    let modelURL = NSBundle.mainBundle().URLForResource("Streak", withExtension: "momd")!
    let managedObjectModel = NSManagedObjectModel(contentsOfURL: modelURL)!

    // Check if we are running as test or not
    let environment = NSProcessInfo.processInfo().environment as [String : AnyObject]
    let isTest = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"

    // Create the module name
    let moduleName = (isTest) ? "StreakTests" : "Streak"

    // Create a new managed object model with updated entity class names
    var newEntities = [] as [NSEntityDescription]
    for (_, entity) in enumerate(managedObjectModel.entities) {
        let newEntity = entity.copy() as NSEntityDescription
        newEntity.managedObjectClassName = "\(moduleName).\(entity.name)"
        newEntities.append(newEntity)
    }
    let newManagedObjectModel = NSManagedObjectModel()
    newManagedObjectModel.entities = newEntities

    return newManagedObjectModel
}()

Ответ 3

Я думаю, что получаю схожие результаты. Мне не удалось заставить мои тесты работать с линией

var newDept = NSEntityDescription.insertNewObjectForEntityForName("Department", inManagedObjectContext: moc) as Department

Но я мог бы запустить тесты с помощью:

let entity = NSEntityDescription.entityForName("Department", inManagedObjectContext: moc)
let department = Department(entity: entity!, insertIntoManagedObjectContext: moc)

Моя сущность выглядит следующим образом:

@objc(Department)
class Department: NSManagedObject {

    @NSManaged var department_description: String
    ...
}

Ответ 4

Пример кода от Ludovic не охватывает сущности. Поэтому при установке родительского объекта в CoreData приложение аварийно завершает работу.

Адаптирован код для учета сущностей:

private func createManagedObjectModel() {

    // Get module name
    var moduleName: String = "ModuleName"
    let environment = NSProcessInfo.processInfo().environment as! [String : AnyObject]
    let isTest = (environment["XCInjectBundle"] as? String)?.pathExtension == "xctest"
    if isTest { moduleName = "ModuleNameTests" }

    // Get model
    let modelURL = NSBundle.mainBundle().URLForResource(self.storeName, withExtension: "momd")!
    let model = NSManagedObjectModel(contentsOfURL: modelURL)!

    // Create entity copies
    var newEntities = [NSEntityDescription]()
    for (_, entity) in enumerate(model.entities) {
        let newEntity = entity.copy() as! NSEntityDescription
        newEntity.managedObjectClassName = "\(moduleName).\(entity.managedObjectClassName)"
        newEntities.append(newEntity)
    }

    // Set correct subentities
    for (_, entity) in enumerate(newEntities) {
        var newSubEntities = [NSEntityDescription]()
        for subEntity in entity.subentities! {
            for (_, entity) in enumerate(newEntities) {
                if subEntity.name == entity.name {
                    newSubEntities.append(entity)
                }
            }
        }
        entity.subentities = newSubEntities
    }

    // Set model
    self.managedObjectModel = NSManagedObjectModel()
    self.managedObjectModel.entities = newEntities
}

Ответ 5

Мне также пришлось столкнуться с подобной проблемой, когда я попытался написать примеры unit test для примера приложения (MedicationSchedulerSwift3.0), написанный в Swift 3.0, кроме внедрения решения, предоставленного johnford. Я создал категорию на XCTestCase, чтобы настроить NSManagedObjectContext с хранилищем в памяти, используя приведенный ниже код:

//  XCTestCase+CoreDataHelper.swift

import CoreData
import XCTest
@testable import Medication

extension XCTestCase {
    func setUpInMemoryManagedObjectContext() -> NSManagedObjectContext {
        let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main])!

        let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)

        do {
            try persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)
        } catch {
            print("Adding in-memory persistent store failed")
        }

        let managedObjectContext = NSManagedObjectContext(concurrencyType:.privateQueueConcurrencyType)
        managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator

        return managedObjectContext
    }
}

И использовал его вот так:

//  NurseTests.swift

import XCTest
import CoreData
@testable import Medication

class NurseTests: XCTestCase {
    var managedObjectContext: NSManagedObjectContext?

    //MARK: Overriden methods
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
        if managedObjectContext == nil {
            managedObjectContext = setUpInMemoryManagedObjectContext()
        }
    }

//MARK:- Testing functions defined in Nurse.swift
    // testing : class func addNurse(withEmail email: String, password: String, inManagedObjectContext managedObjectContext: NSManagedObjectContext) -> NSError?
    func testAddNurse() {
        let nurseEmail = "[email protected]"
        let nursePassword = "clara"

        let error = Nurse.addNurse(withEmail: nurseEmail, password: nursePassword, inManagedObjectContext: managedObjectContext!)
        XCTAssertNil(error, "There should not be any error while adding a nurse")
    }
}

Если кому-то нужно больше примеров, они могут посмотреть здесь unit test - MedicationTests