Core Data 是一个强大的框架,用于管理 iOS 和 macOS 应用程序的数据模型。它可以帮助开发者快速地创建和维护数据库,并且可以轻松地将数据存储在本地文件中。Core Data 提供了一个面向对象的方法来管理数据,使得开发者可以快速而有效地处理大量的数据。
Core Data 的核心是一个关系型数据库,它使用 SQLite 作为存储引擎。它允许开发者将多个表格连接起来,并使用 SQL 语句来执行复杂的数据库查询。此外,Core Data 还包含一些高级功能,如对象图形映射 (Object Graph Mapping)、内存管理 (Memory Management) 和性能优化 (Performance Optimization) 等。
// 创建 Core Data Stack let persistentContainer = NSPersistentContainer(name: "MyDataModel") persistentContainer.loadPersistentStores { (description, error) in if let error = error { fatalError("Unresolved error \(error)") } } // 获取上下文对象 let context = persistentContainer.viewContext // 创建新的实体对象 let newEntity = NSEntityDescription.insertNewObject(forEntityName: "MyEntity", into: context) as! MyEntity // MyEntity 是你创建的实体名称 // 这里你可以保存新创建的实体对象 try context.save() // 查询所有实体对象 let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "MyEntity") do { let results = try context.fetch(fetchRequest) for result in results as! [NSManagedObject] { print("result: \(result)") } } catch { print("Error fetching data") }
原文出处: http://chengway.in/post/ji-zhu/core-data-by-tutorials-bi-ji-liu
今天我们来关注一下CoreData的单元测试,其实在写程序之前,先写测试,将问题一点点分解,也是TDD所倡导的做法,这也是我今年所期望达成的一个目标,新开项目按TDD的流程来做,以后也会整理些这方面的东西。如果你对CoreData的其他方面感兴趣请查看我之前的笔记或直接购买《Core Data by Tutorials》
作者列举了一系列单元测试的好处:帮助你在一开始就组织好项目的结构,可以不用操心UI去测试核心功能。单元测试还可以方便重构。还可以更好地拆分UI进行测试。
这章主要焦距在XCTest这个框架来测试Core Data程序,多数情况下Core Data的测试要依赖于真实的Core Data stack,但又不想将单元测试的test data与你手动添加的接口测试弄混,本章也提供了解决方案。
本章要测试的是一个关于野营管理的APP,主要管理营地、预订(包括时间表和付款情况)。作者将整个业务流程分解为三块:
由于swift内部的访问控制,app和其test分别属于不同的targets和不同的modules,因此你并不能普通地从tests中访问app中的classes,这里有两个解决办法:
作者提供的实例中,已经将要测试的类和方法标记为public的了,现在就可以对Core Data部分进行测试了,作者在测试开始前给了一些建议:
Good unit tests follow the acronym FIRST:
• Fast: If your tests take too long to run, you won’t bother running them.
• Isolated: Any test should function properly when run on its own or before or after any other test.
• Repeatable: You should get the same results every time you run the test against the same codebase.
• Self-verifying: The test itself should report success or failure; you shouldn’t have to check the contents of a file or a console log.
• Timely: There’s some benefit to writing the tests after you’ve already written the code, particularly if you’re writing a new test to cover a new bug. Ideally, though, the tests come first to act as a specification for the functionality you’re developing.
为了达到上面提到“FIRST”目标,我们需要修改Core Data stack使用in-memory store而不是SQLite-backed store。具体的做法是为test target创建一个CoreDataStack的子类来修改store type。
class TestCoreDataStack: CoreDataStack {
override init() {
super.init()
self.persistentStoreCoordinator = {
var psc: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel:
self.managedObjectModel)
var error: NSError? = nil
var ps = psc!.addPersistentStoreWithType(
NSInMemoryStoreType, configuration: nil,
URL: nil, options: nil, error: &error)
if (ps == nil) {
abort()
}
return psc
}()
}
}
单元测试需要将APP的逻辑拆分出来,我们创建一个类来封装这些逻辑。作者这里创建的第一个测试类为CamperServiceTests是XCTestCase的子类,用来测试APPCamperService类中的逻辑
import UIKit
import XCTest
import CoreData
import CampgroundManager
//
class CamperServiceTests: XCTestCase {
var coreDataStack: CoreDataStack!
var camperService: CamperService!
override func setUp() {
super.setUp()
coreDataStack = TestCoreDataStack()
camperService = CamperService(managedObjectContext: coreDataStack.mainContext!, coreDataStack: coreDataStack)
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
coreDataStack = nil
camperService = nil
}
func testAddCamper() {
let camper = camperService.addCamper("Bacon Lover", phoneNumber: "910-543-9000")
XCTAssertNotNil(camper, "Camper should not nil")
XCTAssertTrue(camper?.fullName == "Bacon Lover")
XCTAssertTrue(camper?.phoneNumber == "910-543-9000")
}
setUp会在每次测试前被调用,这里可以创建一些测试需要用到东西,而且因为使用的是in-memory store,每次在setUp中创建的context都是全新的。tearDown相对于setUp,是在每次test结束后调用,用来清除一些属性。上面的例子主要测试了addCamper()方法。
这里注意的就是该测试创建的对象和属性都不会保存在任何store中的。
关于异步测试,这里用到了两个context,一个root context运行在后台线程中,另外一个main context是root context的子类,让context分别在正确的线程中执行其实也很简单,主要采用下面两种方法:
测试第二种performBlock()方法时可能会需要些技巧,因为数据可能不会立即得到,还好XCTestCase提供了一个叫expectations的新特性。下面展示了使用expectation来完成对异步方法的测试:
let expectation = self.expectationWithDescription("Done!");
someService.callMethodWithCompletionHandler() {
expectation.fulfill()
}
self.waitForExpectationsWithTimeout(2.0, handler: nil)
该特性的关键是要么是expectation.fulfill()被执行,要么触发超时产生一个异常expectation,这样test才能继续。
我们现在来为CamperServiceTests继续增加一个新的方法来测试root context的保存:
func testRootContextIsSavedAfterAddingCamper() {
//1 创建了一个针对异步测试的方法,主要是通过观察save方法触发的通知,触发通知后具体的handle返回一个true。
let expectRoot = self.expectationForNotification(
NSManagedObjectContextDidSaveNotification,
object: coreDataStack.rootContext) {
notification in
return true
}
//2 增加一个camper
let camper = camperService.addCamper("Bacon Lover",
phoneNumber: "910-543-9000")
//3 等待2秒,如果第1步没有return true,那么就触发error
self.waitForExpectationsWithTimeout(2.0) {
error in
XCTAssertNil(error, "Save did not occur")
}
}
这一节新建了一个CampSiteServiceTests Class 对CampSiteService进行测试,具体code形式与上一节类似,添加了测试testAddCampSite()和testRootContextIsSavedAfterAddingCampsite(),作者在这里主要展示了TDD的概念。
Test-Driven Development (TDD) is a way of developing an application by writing a test first, then incrementally implementing the feature until the test passes. The code is then refactored for the next feature or improvement.
根据需求又写了一个testGetCampSiteWithMatchingSiteNumber()方法用来测试getCampSite(),因为campSiteService.addCampSite()方法在之前的测试方法中已经通过测试了,所以这里可以放心去用,这就是TDD的一个精髓吧。
func testGetCampSiteWithMatchingSiteNumber(){
campSiteService.addCampSite(1, electricity: true,
water: true)
let campSite = campSiteService.getCampSite(1)
XCTAssertNotNil(campSite, "A campsite should be returned")
}
func testGetCampSiteNoMatchingSiteNumber(){
campSiteService.addCampSite(1, electricity: true,
water: true)
let campSite = campSiteService.getCampSite(2)
XCTAssertNil(campSite, "No campsite should be returned")
}
写完测试方法运行一下CMD+U,当然通不过啦,我们还没有实现他。现在为CampSiteService类添加一个getCampSite()方法:
public func getCampSite(siteNumber: NSNumber) -> CampSite? {
let fetchRequest = NSFetchRequest(entityName: "CampSite") fetchRequest.predicate = NSPredicate(
format: "siteNumber == %@", argumentArray: [siteNumber])
var error: NSError?
let results = self.managedObjectContext.executeFetchRequest(
fetchRequest, error: &error)
if error != nil || results == nil {
return nil
}
return results!.first as CampSite?
}
现在重新CMD+U一下,就通过了。
最后一节主要针对APP中的ReservationService类进行测试,同样的是创建一个ReservationServiceTests测试类,这个test类的setUP和tearDown与第三节类似。只不过多了campSiteService与camperService的设置。在testReserveCampSitePositiveNumberOfDays()方法中对ReservationService类里的reserveCampSite()进行测试后,发现没有对numberOfNights有效性进行判断,随后进行了修改,这也算是展示了单元测试的另一种能力。作者是这么解释的:不管你对这些要测试的code有何了解,你尽肯能地针对这些API写一些测试,如果OK,那么皆大欢喜,如果出问题了,那意味着要么改进code要么改进测试代码。
原文链接:http://www.aosabook.org/en/gdb.html作者:Stan ShebsGDB, 即GNU调试器(GNU Debugger)。它诞生自开源软件基金会(Free...
原文链接: http://www.aosabook.org/en/gpsd.html作者:Eric Raymond译者:Liuli Chen(陈旒俐)GPSD是一系列管理GPS设备和其他...
jQuery 杂项 data() 方法jQuery 杂项方法实例 向 div 元素附加数据,然后取回该数据:$(#btn1).click(function(){ $(div).data(g...
jQuery event.stopImmediatePropagation() 方法jQuery 事件方法实例 执行第一个事件处理程序,并阻止剩下的事件处理程序被执行:...
jQuery event.which 属性jQuery 事件方法实例 返回哪个键盘键被按下:$(input).keydown(function(event){$(div).html(Key:+ even...