2014-07-09 6 views
16

Sto lavorando a un progetto iOS che utilizza i dati principali. Sto usando rapido. Lo stack di Core Data è impostato correttamente e sembra tutto a posto. Ho creato una classe per un'entità (NSManagedObject) denominata TestEntity. La classe si presenta così:Swift non può testare i dati di base nei test Xcode?

import UIKit 
import CoreData 

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

Così, poi cerco di inserire un nuovo TestEntity in codice utilizzando questa riga di codice:

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

Allora ottengo questo errore:

enter image description here

Ho visto alcune risposte sullo stack overflow che dicono che devo preoccuparmi del nome del modulo. Così poi ho guardato che fino ai documenti: https://developer.apple.com/library/content/documentation/Swift/Conceptual/BuildingCocoaApps/WritingSwiftClassesWithObjective-CBehavior.html

Poi sono andato nell'entità dei dati di base per TestEntity e nel campo classe I è entrato myAppName.TestEntity

Quando eseguo l'applicazione di questa linea:

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

mi dà ancora lo stesso errore.

Cos'altro potrei fare di sbagliato?

EDIT: Così, ero in grado di rendere l'applicazione va in crash più cambiando la classe TestEntity NSManagedObject a: import UIKit importazione CoreData

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

Così, ho aggiunto il @objc (TestEntity) dentro. Funziona con o senza l'aggiunta dell'appName prima del nome della classe TestEntity nell'ispettore del modello di dati dati nucleo.

Questo funziona, ma, quando ho eseguito test questa linea ancora crash:

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

Così ho scoperto che questo è un problema per gli altri: How to access Core Data generated Obj-C classes in test targets?

Come può otteniamo dati di base per lavorare nei test in rapido. NON sto usando un'intestazione di bridging nel target dell'app e tutto funziona alla grande. L'obiettivo del test si arresta comunque.

Come posso correggere il target di test in modo che possa eseguire test di dati fondamentali?

+0

Provare questo: http://stackoverflow.com/a/26568813/438063 – Lucien

+0

Vedere alcune note in più [in questa risposta SO] [1] sull'approccio "aggiunta @objc (ClassName)". [1]: http://stackoverflow.com/a/29445352/2466193 –

+0

vedere alcune più note [in questo SO rispondere] [1] sulla '@objc aggiungendo (NomeClasse)' approccio. [1]: http://stackoverflow.com/a/29445352/2466193 –

risposta

18

Con Xcode 7, e @testable, non dovrebbe più essere necessario aggiornare il managedObjectClassName o utilizzare altri hack. Ecco cosa ho fatto per farlo funzionare in Xcode 7.2.

  1. Impostare l'applicazione host di destinazione test e selezionare "Consenti test API di applicazioni host".

enter image description here

  1. Assicurarsi nessuno delle classi regolari hanno un abbonamento di destinazione che punta al bersaglio di prova. Solo le classi con codice di test unitario devono essere impostate sulla destinazione Test.

enter image description here

  1. aggiungere la riga @testable in cima tutte le classi di test:
import XCTest 
@testable import MyApp 

class MyAppTests: XCTestCase { 
} 

Se hai ancora problemi si consiglia di provare questi ulteriori suggerimenti: https://forums.developer.apple.com/message/28773#28949

Ho combattuto con questo per un po 'quindi spero che sia di aiuto s qualcuno altro fuori.

+0

alleato utile ... Ha funzionato alla grande +1 – Sabby

5

Penso di ottenere risultati simili a voi.Sono stato in grado di ottenere il mio test che lavorano con la linea

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

Ma ho potuto ottenere i test in esecuzione con:

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

mio Entity assomiglia:

@objc(Department) 
class Department: NSManagedObject { 

    @NSManaged var department_description: String 
    ... 
} 
+1

posso vedere come funziona per la creazione di nuove entità. Ma cosa succede se hai bisogno di usare cast in seguito, ad esempio con le richieste di recupero, che restituiranno le entità che devi cast nei tipi appropriati? –

6

E 'perché il CoreData il framework è ancora in Objective-C. usi Swift classi namespace-, così per CoreData per trovare le vostre classi veloci è necessario specificare il nome della classe con il suo namespace in questo modo:

enter image description here

Il problema vostro avrà è che la vostra applicazione non ha lo stesso spazio dei nomi come quando esegui i test. <AppName>.<ClassName> vs <AppName>Tests.<ClassName>

EDIT: Soluzione per l'esecuzione come applicazione e test

Ho appena scritto un pezzo di codice per risolvere il <AppName>.<ClassName> vs <AppName>Tests.<ClassName> problema. La soluzione che utilizzo in questo momento (Xcode 6.1) NON riempie il campo Class nell'interfaccia utente CoreData (mostrato sopra) e lo fa invece nel codice.

Questo codice rileverà se si esegue come App vs Test e si utilizza il nome del modulo corretto e si aggiorna lo 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

Grazie, è stato molto utile! –

+0

Questa è la soluzione che funziona per me e ho inserito questo codice pezzo in AppDelegate per farlo funzionare. Per qualche ragione, non sono in grado di farlo funzionare quando l'ho usato direttamente nei miei test di unità. E la risposta di Oliver Shaw funziona solo per target non test e non la usiamo insieme a questa soluzione perché ho trovato che questa soluzione non funzionava correttamente. – nybon

+0

Mi piace l'idea e vedo come potrebbe funzionare ... a patto che non si usi NSPersistentDocument su MacOS. Il problema sembra essere che NSPersistentDocument viene fornito con un contesto, già pronto con un modello. L'inizializzazione di un discendente NSManagedObject fallisce quindi, presumo perché il modello interno non conosce l'entità. :-( –

1

L'esempio di codice di Ludovic non copre le sottovalutazioni. Pertanto, quando si imposta un'entità padre in CoreData, l'app si arresta in modo anomalo.

Adattato il codice per tenere sottoentità in considerazione:

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

Ho anche affrontato un problema simile quando ho provato a scrivere casi di test di unità per un'app campione (MedicationSchedulerSwift3.0) scritta in Swift 3.0, oltre all'implementazione di solution provided by johnford Ho creato una categoria su XCTestCase per configurare un NSManagedObjectContext con un archivio in memoria utilizzando sottostante codice:

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

e lo ha utilizzato in questo modo:

// 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") 
    } 
} 

Nel caso in cui, se qualcuno ha bisogno di più esempi possono guardare casi di test di unità qui - MedicationTests

Problemi correlati