代码之家  ›  专栏  ›  技术社区  ›  Adrian

如何将核心数据用于主应用程序和单元测试?

  •  0
  • Adrian  · 技术社区  · 7 年前

    当我尝试使用核心数据时 NSInMemoryStoreType 对于单元测试,我总是会遇到以下错误:

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

    这是我创建核心数据堆栈的目标:

    public enum StoreType {
        case sqLite
        case binary
        case inMemory
        .................
    }
    
        public final class CoreDataStack {
            var storeType: StoreType!
            public init(storeType: StoreType) {
                self.storeType = storeType
            }
    
            lazy var persistentContainer: NSPersistentContainer = {
                let container = NSPersistentContainer(name: "Transaction")
                container.loadPersistentStores(completionHandler: { (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() {
                for store in persistentContainer.persistentStoreCoordinator.persistentStores {
                    guard let url = store.url else { return }
    
                    try! persistentContainer.persistentStoreCoordinator.remove(store)
                    try! FileManager.default.removeItem(at: url)
                }
            }
        }
    

    这就是我在单元测试项目中使用它的方式:

    class MyTests: XCTestCase {
    
        var context: NSManagedObjectContext!
        var stack: CoreDataStack!
    
        override func setUp() {
            stack = CoreDataStack(storeType: .inMemory)
            context = stack.context
        }
    
        override func tearDown() {
            stack.reset()
            context = nil
        }
    }
    

    从我读到的 here 这似乎和我的问题是一样的,每次测试后我都要清理所有的东西,我(认为)我正在做的事情。

    我打扫得不对吗?还有别的方法吗?

    0 回复  |  直到 7 年前
        1
  •  1
  •   James Bedford    7 年前

    是吗 CoreDataStack 在你的申请中初始化的班级?例如,在 AppDelegate 班当单元测试运行时,它将初始化 AppDelegate 测试开始前的一段时间。我相信这是为了让你的测试可以调用应用程序中的任何东西来测试它 @testable import MyApp .如果您正在通过 AppDelegate 在里面 MyTests 然后将加载两次核心数据堆栈。

    注意,有两个或更多 NSPersistentContainer 实例指两个或多个实例 NSManagedObjectModel 实例将加载到内存中,这就是问题的原因。这两种型号都提供了额外的功能 NSManagedObject 运行时的子类。然后,当您尝试使用这些子类中的一个时,运行时不知道该使用哪一个(即使它们是相同的,它只会看到它们具有相同的名称)。我想如果 NSManagedObjectModel 可以处理这种情况,但目前由开发人员来确保加载的实例不会超过一个。

        2
  •  1
  •   Neil Smith    5 年前

    我知道这个问题由来已久,但我最近遇到了这个问题,在其他地方没有找到答案。

    基于@JamesBedford的答案,设置核心数据堆栈的一种方法是:

    1. 确保你只有一个 单实例 属于 CoreDataStack 在应用程序和测试目标中。不要在测试目标中创建新实例。在你的应用程序target中,你可以像James建议的那样使用单身。或者,如果要在AppDelegate中保留对核心数据堆栈的强引用,并在启动时进行初始化,请在应用程序目标中提供一个方便的静态属性,以便从测试目标访问。比如:
    extension CoreDataStack
        static var shared: CoreDataStack {
            (UIApplication.shared.delegate as! AppDelegate).stack
        }
    }
    
    1. 在Xcode中将环境变量添加到测试方案中。转到Xcode>编辑方案>测试>争论>环境变量。添加新的名称-值对,例如:name= "persistent_store_type" ,价值= "in_memory" .然后,在运行时,在 CoreDataStack 初始化器,您可以使用 ProcessInfo .
    final class CoreDataStack {
        
        let storeType: StoreType
        
        init() {
            if ProcessInfo.processInfo.environment["persistent_store_type"] == "in_memory" {
                self.storeType = .inMemory
            } else {
                self.storeType = .sqlLite
            }
        }
        
    }
    

    从这里开始,您的测试目标现在将使用。inMemory持久存储类型,不会创建SQLLite存储。您甚至可以添加一个单元测试来断言:)