运行第二个单元测试时核心数据实体名称为零

Posted

技术标签:

【中文标题】运行第二个单元测试时核心数据实体名称为零【英文标题】:Core Data entity name is nil while running a second unit test 【发布时间】:2018-12-08 10:38:55 【问题描述】:

我正在尝试为我的核心数据代码添加一些单元测试。但我总是有这个问题,第一个测试总是正确运行,但第二个测试崩溃,因为实体名称为零。

我也收到此错误:

Multiple NSEntityDescriptions claim the NSManagedObject subclass 'Gym.Exercise' so +entity is unable to disambiguate.

Failed to find a unique match for an NSEntityDescription to a managed object subclass

所以我的猜测是我在 tearDown(). 中没有做正确的事情

override func setUp() 
    super.setUp()

    coreDataStack = CoreDataStack(storeType: .inMemory)
    context = coreDataStack.context


override func tearDown() 
    coreDataStack.reset()
    context = nil
    super.tearDown()

这是我的CoreDataStack 课程:

final class CoreDataStack 
    var storeType: StoreType!
    public init(storeType: StoreType) 
        self.storeType = storeType
    

    lazy var persistentContainer: NSPersistentContainer = 
        let container = NSPersistentContainer(name: "Gym")
        container.loadPersistentStores  description, error in
            if let error = error 
                fatalError("Unresolved error \(error), \(error.localizedDescription)")
             else 
                description.type = self.storeType.type

            
        

        return container
    ()

    public var context: NSManagedObjectContext 
        return persistentContainer.viewContext
    

    public func reset() 
        guard let store = persistentContainer.persistentStoreCoordinator.persistentStores.first else  fatalError("No store found")
        guard let url = store.url else  fatalError("No store URL found")

        try! FileManager.default.removeItem(at: url)
        NSPersistentStoreCoordinator.destroyStoreAtURL(url: url)
    

以及destroyStoreAtURL的定义:

extension NSPersistentStoreCoordinator 
    public static func destroyStoreAtURL(url: URL) 
        do 
            let psc = self.init(managedObjectModel: NSManagedObjectModel())
            try psc.destroyPersistentStore(at: url, ofType: NSSQLiteStoreType, options: nil)
         catch let e 
            print("failed to destroy persistent store at \(url)", e)
        
    

我过去使用此代码进行单元测试并且它有效,不同之处在于过去我在编辑器中设置 NSManagedObject 类时使用了以下配置:

Module - Global namespace
Codegen - Class Definition

现在我使用:

Module - Current Product Module
Codegen - Manual/None

因为我想手动添加我的课程。

那么有人知道为什么现在的行为不同了吗?

edit - 我的 NSManagedObject 扩展助手(在尝试检索实体名称时,错误发生在 fetch() 方法的第一行):

extension Managed where Self: NSManagedObject 

    public static var entityName: String 
        return entity().name!
    

    public static func fetch(in context: NSManagedObjectContext, configurationBlock: (NSFetchRequest<Self>) -> () =  _ in ) -> [Self] 
        let request = NSFetchRequest<Self>(entityName: Self.entityName)
        configurationBlock(request)
        return try! context.fetch(request)
    

    public static func count(in context: NSManagedObjectContext, configure: (NSFetchRequest<Self>) -> () =  _ in ) -> Int 
        let request = NSFetchRequest<Self>(entityName: entityName)
        configure(request)
        return try! context.count(for: request)
    

    public static func findOrFetch(in context: NSManagedObjectContext, matching predicate: NSPredicate) -> Self? 
        guard let object = materializeObject(in: context, matching: predicate) else 
        return fetch(in: context)  request in
                request.predicate = predicate
                request.returnsObjectsAsFaults = false
                request.fetchLimit = 1
            .first
        

        return object
    

    public static func materializeObject(in context: NSManagedObjectContext, matching predicate: NSPredicate) -> Self? 
        for object in context.registeredObjects where !object.isFault 
           guard let result = object as? Self, predicate.evaluate(with: result) else 
               continue
           

           return result
        

        return nil
    

    public static func findOrCreate(in context: NSManagedObjectContext, matching predicate: NSPredicate, configure: (Self) -> ()) -> Self 
        guard let object = findOrFetch(in: context, matching: predicate) else 
           let newObject: Self = context.insertObject()
           configure(newObject)
           return newObject
        

        return object
    

【问题讨论】:

请显示出错的代码。 @pbasdf: 完成,在 fetch(...) 方法内部发生错误,尝试检索实体名称时的第一行 考虑到该错误,首先想到的是您可能无意中在模型编辑器中定义了具有相同类的两个实体。您是否检查了所有实体定义以确保它们具有正确的类? @pbasdf:模型中只添加了一个实体,并且在代码中由我定义了一次。所以什么也没产生。 【参考方案1】:

您应该尝试删除所有找到的 persistentStore 实例,而不是删除第一个。

尝试使用以下替换重置功能:

public func reset() 
    let stores = persistentContainer.persistentStoreCoordinator.persistentStores
    guard !stores.isEmpty else 
        fatalError("No store found")
    
    stores.forEach  store in
        guard let url = store.url else  fatalError("No store URL found")

        try! FileManager.default.removeItem(at: url)
        NSPersistentStoreCoordinator.destroyStoreAtURL(url: url)
    

看看你是否还有问题。

【讨论】:

现在我收到以下错误:尝试获取请求时出现 NSSQLiteErrorDomain = 522。所以当在 fetch(...) 方法中调用“return try!context.fetch(request)”时。 而再次运行时,总是在加载持久化存储的时候报错,同样,522错误。

以上是关于运行第二个单元测试时核心数据实体名称为零的主要内容,如果未能解决你的问题,请参考以下文章

单元测试时托管对象上下文为零restkit

带有 jacoco 插件的 SonarQube 覆盖百分比显示为零,但可以看到单元测试的数量

MockEJB - JUnit Mockito - 无法在第二个单元测试中重新绑定模拟 EJB

Fastlane 扫描显示零单元测试

Xcode:运行 XC 单元测试时,模块名称“”不是有效标识符

测试篇——初探单元测试