如何将 Core Data 用于主应用程序和单元测试?

Posted

技术标签:

【中文标题】如何将 Core Data 用于主应用程序和单元测试?【英文标题】:How to use Core Data for main app and unit tests? 【发布时间】:2019-03-10 14:29:48 【问题描述】:

当我尝试将 Core Data 与 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 看来,这似乎与我遇到的问题相同,我必须在每次测试后清理所有内容,我(认为)我正在这样做。

我没有正确清理吗?还有其他方法吗?

【问题讨论】:

【参考方案1】:

CoreDataStack 类是否已在您的应用程序中初始化?例如,在AppDelegate 类中?当单元测试运行时,它将在测试运行前的某个时间初始化AppDelegate。我相信这是为了让您的测试可以调用应用程序中的任何内容来测试它,就像@testable import MyApp 一样。如果您通过AppDelegate MyTests 中初始化核心数据堆栈,那么您将加载核心数据堆栈两次。

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

【讨论】:

现在我只是在单元测试中使用 CoreDataStack,仅此而已。但我确实相信一些 NSManagedObjectModel 是如何被多次创建的。它总是在第二个单元测试时崩溃,第一个工作正常。所以我认为答案是确保 NSManagedObjectModel 没有被多次创建。 您是否尝试过在您的应用程序中设置一次CoreDataStack,然后从您的测试中访问它(而不是在您的测试中设置它)? 是的,行为是一样的 您只希望有 1 个 CoreDataStack 实例存在,对吧?您是否尝试过使其成为单身人士?您可以在 init 方法中放置一个断点,并准确查看每个实例的创建位置。【参考方案2】:

我知道这个问题很老,但是,我最近遇到了这个问题,并没有在其他地方找到答案。

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

    确保您的应用中在应用和测试目标中只有一个CoreDataStack单个实例。不要在测试目标中创建新实例。在您的应用程序目标中,您可以按照 James 的建议使用单例。或者,如果您在 AppDelegate 中保持对 Core Data 堆栈的强引用并在启动时进行初始化,请在您的应用程序目标中提供一个方便的静态属性以从您的测试目标访问。比如:
extension CoreDataStack
    static var shared: CoreDataStack 
        (UIApplication.shared.delegate as! AppDelegate).stack
    

    在 Xcode 中将环境变量添加到您的测试方案。转到 Xcode > 编辑方案 > 测试 > 参数 > 环境变量。添加一个新的名称-值对,例如:name = "persistent_store_type", value = "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 存储。你甚至可以添加一个单元测试断言这样:)

【讨论】:

以上是关于如何将 Core Data 用于主应用程序和单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Core Data 有效地保存 UI/主线程中所做的更改?

Core Audio:用于提升信号电平的音频单元

在 Core Data 中创建关系以执行删除

如何对 CloudKit/Core Data 进行单元测试?

如何过滤在 Core Data 中有对象的表视图?

Core Data 3 托管对象上下文