如何编写核心数据的单元测试用例
在本教程中,您将了解如何在Xcode中构造Core Data Stack来测试您的Core Data应用程序。 这并不是应该的那么简单,因为大多数测试将取决于有效的核心数据堆栈。 您不想破坏单元测试中的数据,以免干扰您自己在模拟器或设备上进行的手动测试。
访问控制
默认情况下,swift中的类具有“内部”访问级别。 这意味着您只能从它们自己的模块中访问它们。 由于应用程序和测试位于单独的模块中,因此您将无法在测试中从应用程序访问类。
如何解决这个问题
1.)您可以将应用程序中的类和方法标记为公共,以使其在测试中可见。
2)您可以在单元测试中的任何导入前面添加Swift关键字@testable,以访问该类中导入的所有内容。
@testable导入CoreDataUnitTesting
添加@testable是执行此操作的更好方法,因为您不必将您的类和方法标记为公开以进行测试。
您应用中的核心数据栈将像这样::
类CoreDataStack {
public init(){
}
懒惰的varpersistentContainer:NSPersistentContainer = {
让容器= NSPersistentContainer(
名称:“ CoreDataUnitTesting”)
container.loadPersistentStores(
completeHandler:{中的(storeDescription,错误)
如果让error = error as NSError? {
fatalError(“未解决的错误\(错误),
\(error.userInfo)“)
}
})
返回容器
}()
var applicationDocumentsDirectory:NSURL = {
让urls = FileManager.default.urls(用于:
.documentDirectory,位于:.userDomainMask)
返回urls [urls.count-1]作为NSURL
}()
公开varmanagedObjectModel:NSManagedObjectModel = {
var modelPath = Bundle.main.path(
forResource:“ CoreDataUnitTesting”,
ofType:“ momd”)
var modelURL = NSURL.fileURL(withPath:modelPath!)
var model = NSManagedObjectModel(contentsOf:modelURL)!
退货模式
}()
公共惰性varpersistentStoreCoordinator:NSPersistentStoreCoordinator = {
让url = self.applicationDocumentsDirectory.appendingPathComponent(“ CoreDataUnitTesting.sqlite”)
var options = [NSInferMappingModelAutomaticallyOption:true,NSMigratePersistentStoresAutomaticallyOption:true]
var psc = NSPersistentStoreCoordinator(managedObjectModel:self.managedObjectModel)
做{
尝试psc.addPersistentStore(ofType:NSSQLiteStoreType,configurationName:nil,at:url,options:options)
} {
NSLog(“创建持久性存储\(错误)时出错”)
致命错误()
}
返回psc
}()
公共惰性var rootContext:NSManagedObjectContext = {
var context:NSManagedObjectContext = NSManagedObjectContext(concurrencyType:.privateQueueConcurrencyType)
context.persistentStoreCoordinator = self.persistentStoreCoordinator
返回上下文
}()
// MARK:-核心数据保存支持
func saveContext(){
让上下文= persistentContainer.viewContext
如果context.hasChanges {
做{
尝试context.save()
} {
让nserror =错误为NSError
fatalError(“未解决的错误\(nserror),\(nserror.userInfo)”)
}
}
}
}
核心数据测试应隔离,可重复且快速 。 由于您的项目使用磁盘上数据库文件中的核心数据,因此无论是单独运行还是在其他任何测试之前或之后,它都应该可以正常运行。 这听起来不是很孤立 ,因为一个测试的数据可能会写到数据库中,并可能影响其他测试。 听起来也不是很可重复 ,因为每次运行测试时,数据都会在数据库文件中建立。 您可以在运行每个测试之前手动删除并重新创建数据库文件,但这不会很快 。
解决方案是使用内存中存储而不是SQLite支持的存储 的修改后的Core Data堆栈 。 这将很快并且每次都提供干净的状态。
您的测试核心数据栈应该是这样的,它将位于测试模块中。
类TestCoreDataStack:CoreDataStack {
覆盖init(){
super.init()
self.persistentStoreCoordinator = {
让psc = NSPersistentStoreCoordinator(
managedObjectModel:self.managedObjectModel)
做{
尝试psc.addPersistentStore(ofType:
NSInMemoryStoreType,配置名称:无,
在:无,选项:无)
} {
致命错误()
}
返回psc
}()
}
}
此类是CoreDataStack的子类,并且仅覆盖单个属性的默认值:persistentStoreCoordinator。 由于您要覆盖init()中的值,因此不会使用或实例化CoreDataStack中的持久性协调器。 TestCoreDataStack中的persistentStoreCoordinator仅使用内存中的存储。
而且内存中的存储永不持久化到磁盘上,这意味着您可以实例化堆栈并在测试中写入任意数量的数据。 测试结束后,内存中的存储将自动清除。
让我们来看一个例子。
假设您有一个名为Person的实体,具有以下属性。
您将使用此Person实体 在您的代码中是这样的。
导入CoreData
类PersonService {
让managedObjectContext:NSManagedObjectContext
让coreDataStack:CoreDataStack
公共初始化(managedObjectContext:NSManagedObjectContext,
coreDataStack:CoreDataStack){
self.managedObjectContext = managedObjectContext
self.coreDataStack = coreDataStack
}
@discardableResult
public func addPerson(firstName:String,
lastName:字符串,
phoneNumber:字符串)->人员? {
让person = NSEntityDescription。
insertNewObject(forEntityName:“ Person”,in :: managedObjectContext)为! 人
person.firstName =名字
person.lastName =姓氏
person.phoneNumber =电话号码
coreDataStack.saveContext()
返回人
}
}
现在我们将为PersonService类编写测试用例
导入XCTest
导入UIKit
@testable import CoreDataUnitTestingclass CoreDataUnitTestingTests:XCTestCase {//标记:属性
var personService:PersonService!
var coreDataStack:CoreDataStack!
覆盖func setUp(){
super.setUp()
//将所有设置的代码放在这里
coreDataStack = TestCoreDataStack()
personService = PersonService(
managedObjectContext:coreDataStack.mainContext,
coreDataStack:coreDataStack)
} var personService:PersonService!
var coreDataStack:CoreDataStack!
覆盖func setUp(){
super.setUp()
//将所有设置的代码放在这里
coreDataStack = TestCoreDataStack()
personService = PersonService(
managedObjectContext:coreDataStack.mainContext,
coreDataStack:coreDataStack)
}
1.)personService和coreDataStack属性将保留对被测personService实例和Core Data Stack的引用。 这些属性是隐式解包的可选属性,因为它们将在setUp中而不是init中初始化。
2.)在每次测试运行之前调用setUp。 这是您创建类中所有单元测试所需的资源的机会。 在这种情况下,我们初始化personService和coreDataStack属性。
每次测试后重置数据都是明智的选择-测试是隔离且可重复的,还记得吗? 使用内存存储并在setUp中创建上下文会为您重置。
3)tearDown与setUp相反,在测试执行后调用。 在这里,该方法将简单地使所有属性为零,并在每次测试后重置coreDataStack。
单击“产品”菜单,然后选择“测试”或键入(Command + U)来运行单元测试。 您应该在Xcode中看到一个绿色的选中标记。
编写可以使用Core Data的单元测试将有助于在代码到达用户之前使其稳定。 虽然Core Data可以帮助您从项目中消除很多容易出错的持久性代码,但是如果使用不正确,它可能是逻辑错误的来源。
快乐编码!