2014-07-09 132 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