如何将 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/主线程中所做的更改?