2014-07-09 74 views
16

我正在使用核心数据的iOS项目。我正在使用swift。核心数据堆栈设置正确,似乎都很好。 我已经为实体(NSManagedObject)创建了一个名为TestEntity的类。 类看起来是这样的:Swift无法在Xcode测试中测试核心数据?

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 

仍然给我同样的错误。

我还能做什么错?

编辑: 所以,我是能够使应用程序不会改变TestEntity NSManagedObject类了崩溃: 进口的UIKit 进口CoreData

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

所以,我加入了@objc(TestEntity)在里面。这适用于在核心数据数据模型检查器中的TestEntity类名称之前添加或不添加appName。

这个工作,但是,当我运行测试此行仍然崩溃

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

所以我发现,这是其他人的问题: How to access Core Data generated Obj-C classes in test targets?

哪有我们获得了核心数据,可以在swift中进行测试。我没有在应用程序目标中使用桥接标头,并且这一切都很好。测试目标仍然崩溃。

如何修复测试目标,以便它可以运行核心数据测试?

+0

试试这个:http://stackoverflow.com/a/26568813/438063 – Lucien

+0

看到一些更多的笔记[在这个SO回答] [1]'加入@objc(ClassName)'的方法。 [1]:http://stackoverflow.com/a/29445352/2466193 –

+0

看到一些更多音符[在该SO回答] [1]的 '加入@objc(类名)' 的方法。 [1]:http://stackoverflow.com/a/29445352/2466193 –

回答

18

在Xcode 7,和@testable,你不再需要更新managedObjectClassName或使用其他黑客。以下是我在Xcode 7.2中的工作原理。

  1. 设置您的测试目标主机应用程序,并选中“允许测试主机应用程序API”。

enter image description here

  • 确保不关你的普通班有一个目标成员指向测试目标。只有具有单元测试代码的类应该设置为测试目标。
  • enter image description here

  • @testable行添加到您的所有测试类的顶部:
  • import XCTest 
    @testable import MyApp 
    
    class MyAppTests: XCTestCase { 
    } 
    

    如果你仍然有问题你可能想尝试这些额外的提示:https://forums.developer.apple.com/message/28773#28949

    我与这一个争取了一段时间,所以我希望它可以帮助s omeone别的了。

    +0

    This was re盟友有帮助...它工作很好+1 – Sabby

    5

    我想我得到了类似的结果给你。我无法得到我的测试与线

    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 
        ... 
    } 
    
    +1

    我可以看到这是如何工作创造新的实体。但是如果您以后需要使用强制类型转换,例如使用获取请求,会返回需要转换为适当类型的实体吗? –

    6

    这是因为CoreData框架仍然在Objective-C中。斯威夫特用途命名空间类,所以CoreData找到你的SWIFT类,你必须指定类名与它的命名空间是这样的:

    enter image description here

    您将具有的问题是,你的应用程序不具有相同的命名空间,就像你正在运行你的测试一样。 <AppName>.<ClassName> VS <AppName>Tests.<ClassName>

    编辑:解决方案运行的应用程序和测试

    我刚写一段代码来解决<AppName>.<ClassName> VS <AppName>Tests.<ClassName>问题。我现在使用的解决方案(Xcode 6.1)不是填充CoreData UI(如上所示)中的Class字段,而是使用代码替代它。

    此代码将检测您是否作为应用程序运行vs测试并使用正确的模块名称并更新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 
    }() 
    
    +0

    谢谢,这非常有帮助! –

    +0

    这是为我工作的解决方案,我把这段代码放在AppDelegate中以使其工作。出于某种原因,我无法直接在单元测试中使用它。 Oliver Shaw的答案仅适用于非测试目标,并且不要与此解决方案一起使用,因为我发现它会使此解决方案无法正常工作。 – nybon

    +0

    我喜欢这个想法,看看它是如何工作的......只要你不在MacOS上使用NSPersistentDocument。问题似乎是NSPersistentDocument带有一个上下文,已经准备好了模型。 NSManagedObject后代初始化然后失败,我假设,因为内部模型不知道实体。 :-( –

    1

    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 
    } 
    
    1

    我也面临类似问题时,我试图写单元测试用例写入夫特3.0一个示例应用程序(MedicationSchedulerSwift3.0),除了实施solution provided by 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") 
        } 
    } 
    

    在情况下,如果有人需要更多的例子,他们可以看到在单元测试用例看过来 - MedicationTests