Swift/OSX-如何在不运行应用程序的情况下提供单元测试核心数据访问和运行测试

Swift/OSX-如何在不运行应用程序的情况下提供单元测试核心数据访问和运行测试,swift,macos,unit-testing,core-data,Swift,Macos,Unit Testing,Core Data,我在我的小而简单的应用程序中使用核心数据。它很好用。在我开始开发应用程序并使其更复杂之前,我想设置一些单元测试。要想实现这一点,你必须跳出一些障碍,但我在SE上找到了让我通过这些障碍的方法。基本思想是必须手动创建对象模型。足够容易;我根据上面链接的一个答案改编了这段代码,问题就解决了 let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main])! let persistentStoreCoordina

我在我的小而简单的应用程序中使用核心数据。它很好用。在我开始开发应用程序并使其更复杂之前,我想设置一些单元测试。要想实现这一点,你必须跳出一些障碍,但我在SE上找到了让我通过这些障碍的方法。基本思想是必须手动创建对象模型。足够容易;我根据上面链接的一个答案改编了这段代码,问题就解决了

let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main])!
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
try! persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)

let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator

NSEntityDescription.entity(forEntityName: "VAgentEditor", in: managedObjectContext)!
太好了。但我也想在不运行应用程序的情况下运行测试,只是为了测试我的低级逻辑。更多的篮筐可以跳过去。这方面也有很多有用的东西,问题解决了

let managedObjectModel = NSManagedObjectModel.mergedModel(from: [Bundle.main])!
let persistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
try! persistentStoreCoordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: nil)

let managedObjectContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
managedObjectContext.persistentStoreCoordinator = persistentStoreCoordinator

NSEntityDescription.entity(forEntityName: "VAgentEditor", in: managedObjectContext)!
当我尝试访问核心数据并在没有应用程序窗口的情况下运行时,就会出现问题。天真地结合了这两个问题的解决方案,我从调用
NSEntityDescription.entity()
中得到了nil。我已经用我能想到的各种方法修改了上面的代码,但是没有用。如果我能充分利用它,从调用
entity()
中得到非零返回,我最终会得到
vageneditor
找不到的消息,或者当我试图将上下文保存到磁盘时,我会得到一个文件,看起来有点半途而废-- 里面有很多东西,但是没有像我尝试上面描述的两种工作场景中的一种时得到的
vageneditor

当我试图将单元测试指向主应用程序的捆绑包时,我收到了各种各样的抱怨,抱怨捆绑包找不到,或者单元测试无法同时使用自己的捆绑包和应用程序捆绑包,还有100个其他的抱怨,我在这几天的黑客攻击后都记不得了


有没有办法让我的单元测试使用CoreData,访问正确的包以获取我在Interface Builder中定义的类,并在不运行主应用程序的情况下运行测试?

如果用于数据库处理,一切都会变得更简单。您可以使用以下代码设置数据库:

let dataStack: DataStack = {
    let dataStack = DataStack(xcodeModelName: "ModelName")
    do {
        try dataStack.addStorageAndWait()
    } catch let error {
        XCTFail("Cannot set up database storage: \(error)")
    }
    return dataStack
}()
假设您拥有以下数据库对象:

class User: NSManagedObject {

    @NSManaged var name: String

    func rename(name: String, transaction: BaseDataTransaction?) {
        guard let user = transaction.edit(self) else {
            return
        }
        user.name = name
    }

}
您可以像这样运行单元测试:

class FileTests: XCTestCase {

    func testRename() {
        // 1. Arrange
        var user: User?
        do {
            try dataStack.perform(synchronous: { transaction in
                user = transaction.create(Into<User>())
                user!.name = "Test"
            })
        } catch let error {
            XCTFail("Cannot perform database transaction: \(error)")
        }

        // 2. Action
        do {
            try dataStack.perform(synchronous: { transaction in
            guard let user = transaction.edit(user!) else {
                return
            }
            user.rename(name: "Renamed", transaction: transaction)
            })
        } catch let error {
            XCTFail("Cannot perform database transaction: \(error)")
        }

        // 3. Assert
        do {
            try dataStack.perform(synchronous: { transaction in
            guard let user = transaction.edit(user!) else {
                return
            }
            XCTAssertEqual(user.name, "Renamed")
            })
        } catch let error {
            XCTFail("Cannot perform database transaction: \(error)")
        }
    }

}
类文件测试:XCTestCase{
func testRename(){
//1.安排
var用户:用户?
做{
尝试dataStack.perform(同步:{transaction in)
user=transaction.create(进入())
user!.name=“测试”
})
}捕捉错误{
XCTFail(“无法执行数据库事务:\(错误)”)
}
//2.行动
做{
尝试dataStack.perform(同步:{transaction in)
guard let user=transaction.edit(user!)else{
返回
}
user.rename(名称:“重命名”,事务:事务)
})
}捕捉错误{
XCTFail(“无法执行数据库事务:\(错误)”)
}
//3.断言
做{
尝试dataStack.perform(同步:{transaction in)
guard let user=transaction.edit(user!)else{
返回
}
xctasertequal(user.name,“重命名”)
})
}捕捉错误{
XCTFail(“无法执行数据库事务:\(错误)”)
}
}
}

这只是开始。您可以简化和改进整个流程,但我希望您能理解。

非常感谢。这是一个学习曲线,但我在测试中使用了它,而不必运行整个应用程序。代码内模式规范是关键——它使您不必使用
.xcdatamodeld
文件,因此没有难看的捆绑包问题。谢谢