如何编写核心数据的单元测试用例

在本教程中,您将了解如何在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可以帮助您从项目中消除很多容易出错的持久性代码,但是如果使用不正确,它可能是逻辑错误的来源。

快乐编码!