版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2020.08.19 星期三 |
前言
數(shù)據(jù)的持久化存儲(chǔ)是移動(dòng)端不可避免的一個(gè)問(wèn)題梗顺,很多時(shí)候的業(yè)務(wù)邏輯都需要我們進(jìn)行本地化存儲(chǔ)解決和完成,我們可以采用很多持久化存儲(chǔ)方案分俯,比如說(shuō)
plist
文件(屬性列表)、preference
(偏好設(shè)置)、NSKeyedArchiver
(歸檔)辣吃、SQLite 3
榆纽、CoreData
仰猖,這里基本上我們都用過(guò)捏肢。這幾種方案各有優(yōu)缺點(diǎn),其中饥侵,CoreData是蘋(píng)果極力推薦我們使用的一種方式鸵赫,我已經(jīng)將它分離出去一個(gè)專(zhuān)題進(jìn)行說(shuō)明講解。這個(gè)專(zhuān)題主要就是針對(duì)另外幾種數(shù)據(jù)持久化存儲(chǔ)方案而設(shè)立躏升。
1. 數(shù)據(jù)持久化方案解析(一) —— 一個(gè)簡(jiǎn)單的基于SQLite持久化方案示例(一)
2. 數(shù)據(jù)持久化方案解析(二) —— 一個(gè)簡(jiǎn)單的基于SQLite持久化方案示例(二)
3. 數(shù)據(jù)持久化方案解析(三) —— 基于NSCoding的持久化存儲(chǔ)(一)
4. 數(shù)據(jù)持久化方案解析(四) —— 基于NSCoding的持久化存儲(chǔ)(二)
5. 數(shù)據(jù)持久化方案解析(五) —— 基于Realm的持久化存儲(chǔ)(一)
6. 數(shù)據(jù)持久化方案解析(六) —— 基于Realm的持久化存儲(chǔ)(二)
7. 數(shù)據(jù)持久化方案解析(七) —— 基于Realm的持久化存儲(chǔ)(三)
8. 數(shù)據(jù)持久化方案解析(八) —— UIDocument的數(shù)據(jù)存儲(chǔ)(一)
9. 數(shù)據(jù)持久化方案解析(九) —— UIDocument的數(shù)據(jù)存儲(chǔ)(二)
10. 數(shù)據(jù)持久化方案解析(十) —— UIDocument的數(shù)據(jù)存儲(chǔ)(三)
11. 數(shù)據(jù)持久化方案解析(十一) —— 基于Core Data 和 SwiftUI的數(shù)據(jù)存儲(chǔ)示例(一)
12. 數(shù)據(jù)持久化方案解析(十二) —— 基于Core Data 和 SwiftUI的數(shù)據(jù)存儲(chǔ)示例(二)
開(kāi)始
首先看下主要內(nèi)容:
測(cè)試代碼是應(yīng)用程序開(kāi)發(fā)的關(guān)鍵部分辩棒,
Core Data
也不例外。 本教程將教您如何測(cè)試Core Data
膨疏。內(nèi)容來(lái)自翻譯一睁。
下面就是寫(xiě)作環(huán)境:
Swift 5, iOS 13, Xcode 11
測(cè)試代碼是應(yīng)用開(kāi)發(fā)過(guò)程中至關(guān)重要的一部分。 盡管測(cè)試最初需要一段時(shí)間才能習(xí)慣佃却,但它還是有很多好處者吁,例如:
- 允許您進(jìn)行更改,而不必?fù)?dān)心應(yīng)用程序的某些部分會(huì)損壞饲帅。
- 加速調(diào)試過(guò)程复凳。
- 迫使您考慮如何以更有條理的方式組織代碼。
并且在本教程中灶泵,您將學(xué)習(xí)如何將測(cè)試的好處應(yīng)用于Core Data
模型育八。
您將使用PandemicReport
,這是一個(gè)簡(jiǎn)單但出色的大流行報(bào)告跟蹤器赦邻。 您將專(zhuān)注于為項(xiàng)目的Core Data
模型編寫(xiě)單元測(cè)試髓棋,并學(xué)習(xí):
- 什么是單元測(cè)試及其重要性。
- 如何編寫(xiě)適合測(cè)試的
Core Data Stack
惶洲。 - 如何對(duì)
Core Data
模型進(jìn)行單元測(cè)試仲锄。 - 關(guān)于
TDD
方法論。
注意:本教程假定您了解Core Data的基礎(chǔ)知識(shí)湃鹊。 如果您不熟悉Core Data儒喊,請(qǐng)首先查看 Getting Started with Core Data Tutorial。
在入門(mén)項(xiàng)目中币呵,您會(huì)找到PandemicTracker
怀愧,這是一個(gè)顯示感染報(bào)告列表的應(yīng)用程序霞赫。
您可以添加新報(bào)告并編輯現(xiàn)有報(bào)告蚌讼。 該應(yīng)用程序使用Core Data
持久保存會(huì)話(huà)之間的報(bào)告。 構(gòu)建并運(yùn)行以簽出該應(yīng)用程序早龟。
該應(yīng)用程序顯示保存在Core Data
中的大流行報(bào)告列表妻柒。 目前扛拨,您沒(méi)有任何報(bào)告。 通過(guò)單擊導(dǎo)航欄中的添加按鈕來(lái)添加一個(gè)举塔。
然后绑警,通過(guò)在text field
中輸入值來(lái)添加報(bào)告條目求泰。
接下來(lái),點(diǎn)擊Save
以將您的報(bào)告保存到Core Data
并關(guān)閉此屏幕计盒。
現(xiàn)在渴频,該列表包含您的條目。
在Xcode中北启,查看要處理的主要文件:
-
CoreDataStack.swift:對(duì)象包裝器卜朗,用于管理應(yīng)用的
Core Data model
層。 - ReportService.swift:管理應(yīng)用的業(yè)務(wù)邏輯咕村。
-
ViewController.swift:顯示保存在
Core Data
中的報(bào)告列表场钉。 點(diǎn)擊+
將顯示新報(bào)告的輸入表單。 -
ReportDetailsTableViewController.swift:顯示所選報(bào)告的詳細(xì)信息懈涛,并允許您編輯現(xiàn)有值逛万。 當(dāng)您在
ViewController
中點(diǎn)擊+
時(shí),這也充當(dāng)輸入形式肩钠。
您將探索為什么為Core Data
編寫(xiě)單元測(cè)試會(huì)比后面幾節(jié)中講的要棘手泣港。
What is Unit Testing?
單元測(cè)試是將項(xiàng)目分解為較小的可測(cè)試代碼段的任務(wù)暂殖。 例如价匠,您可以將iPhone
上的Messages
邏輯分解為較小的功能單元,如下所示:
- 將一個(gè)或多個(gè)收件人分配給該郵件呛每。
- 在文本區(qū)域中寫(xiě)入文本踩窖。
- 添加表情符號(hào)。
- 添加圖像晨横。
- 附加
GIF
洋腮。 - 附加
Animoji
。
盡管這似乎是很多額外的工作手形,但是測(cè)試有很多好處:
- 單元測(cè)試可驗(yàn)證您的代碼是否按預(yù)期工作啥供。
- 編寫(xiě)測(cè)試可以在
bug
投入生產(chǎn)之前就將它們捕獲。 - 測(cè)試還充當(dāng)其他開(kāi)發(fā)人員的文檔库糠。
- 與手動(dòng)測(cè)試相比伙狐,單元測(cè)試可以節(jié)省時(shí)間。
- 在開(kāi)發(fā)過(guò)程中失敗的測(cè)試使您知道某些問(wèn)題瞬欧。
在iOS中贷屎,單元測(cè)試在與您要測(cè)試的應(yīng)用程序相同的環(huán)境中運(yùn)行。 因此艘虎,如果正在運(yùn)行的測(cè)試修改了應(yīng)用的狀態(tài)唉侄,則可能會(huì)導(dǎo)致問(wèn)題。
注意:如果您要開(kāi)始在iOS中進(jìn)行測(cè)試野建,或者想復(fù)習(xí)一下属划,請(qǐng)查看iOS Unit Testing and UI Testing恬叹。
CoreData Stack for Testing
該項(xiàng)目的Core Data stack
當(dāng)前使用SQLite
數(shù)據(jù)庫(kù)作為其存儲(chǔ)。運(yùn)行測(cè)試時(shí)榴嗅,您不希望測(cè)試或虛擬數(shù)據(jù)干擾應(yīng)用程序的存儲(chǔ)妄呕。
要編寫(xiě)好的單元測(cè)試,請(qǐng)遵循首字母縮寫(xiě)詞FIRST
:
- Fast:?jiǎn)卧獪y(cè)試運(yùn)行迅速嗽测。
- Isolated:它們應(yīng)獨(dú)立于其他測(cè)試運(yùn)行绪励。
- Repeatable:每次執(zhí)行測(cè)試都應(yīng)產(chǎn)生相同的結(jié)果。
- Self-verifying:測(cè)試應(yīng)該通過(guò)或失敗唠粥。您無(wú)需檢查控制臺(tái)或日志文件即可確定測(cè)試是否成功疏魏。
- Timely:首先編寫(xiě)測(cè)試,以便它們可以充當(dāng)您添加的功能的藍(lán)圖晤愧。
Core Data
將數(shù)據(jù)寫(xiě)入并保存到模擬器或設(shè)備上的數(shù)據(jù)庫(kù)文件中大莫。由于一項(xiàng)測(cè)試可能會(huì)覆蓋另一項(xiàng)測(cè)試的內(nèi)容,因此您不能將其視為Isolated官份。
由于數(shù)據(jù)保存到磁盤(pán)只厘,因此數(shù)據(jù)庫(kù)中的數(shù)據(jù)會(huì)隨著時(shí)間增長(zhǎng),并且每次測(cè)試運(yùn)行時(shí)環(huán)境的狀態(tài)可能會(huì)有所不同舅巷。結(jié)果羔味,這些測(cè)試是不可Repeatable。
測(cè)試完成后钠右,刪除并重新創(chuàng)建數(shù)據(jù)庫(kù)內(nèi)容并不Fast赋元。
您可能會(huì)想,“好吧飒房,我猜我無(wú)法測(cè)試Core Data
搁凸,因?yàn)樗鼰o(wú)法測(cè)試”。再想一想狠毯。
解決方案是創(chuàng)建一個(gè)使用內(nèi)存存儲(chǔ)in-memory store
而不是當(dāng)前SQLite
存儲(chǔ)的Core Data stack
子類(lèi)护糖。由于內(nèi)存存儲(chǔ)區(qū)不會(huì)持久存儲(chǔ)在磁盤(pán)上,因此當(dāng)測(cè)試完成執(zhí)行時(shí)嚼松,in-memory store
將釋放其數(shù)據(jù)嫡良。
您將在下一部分中創(chuàng)建此子類(lèi)。
1. Adding the TestCoreDataStack
首先惜颇,在PandemicReportTests
組下創(chuàng)建CoreDataStack
的子類(lèi)皆刺,并將其命名為TestCoreDataStack.swift
。
接著凌摄,添加下面到文件中:
import CoreData
import PandemicReport
class TestCoreDataStack: CoreDataStack {
override init() {
super.init()
// 1
let persistentStoreDescription = NSPersistentStoreDescription()
persistentStoreDescription.type = NSInMemoryStoreType
// 2
let container = NSPersistentContainer(
name: CoreDataStack.modelName,
managedObjectModel: CoreDataStack.model)
// 3
container.persistentStoreDescriptions = [persistentStoreDescription]
container.loadPersistentStores { _, error in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
}
// 4
storeContainer = container
}
}
在這里羡蛾,代碼:
- 1) 創(chuàng)建一個(gè)內(nèi)存持久存儲(chǔ)
(persistent store)
。 - 2) 創(chuàng)建一個(gè)
NSPersistentContainer
實(shí)例锨亏,并傳入存儲(chǔ)在CoreDataStack
中的modelName
和NSManageObjectModel
痴怨。 - 3) 將
in-memory persistent store
分配給容器忙干。 - 4) 覆蓋
CoreDataStack
中的storeContainer
。
真好浪藻! 有了這個(gè)類(lèi)捐迫,您就有了為Core Data Model
創(chuàng)建測(cè)試的基線(xiàn)
2. Different Stores
在上面,您使用了一個(gè)內(nèi)存存儲(chǔ)(in-memory store)
爱葵,但是您可能想知道還有哪些其他選擇施戴。 Core Data
中有四個(gè)永久存儲(chǔ)(persistent store)
可用:
-
NSSQLiteStoreType:用于
Core Data
的最常見(jiàn)存儲(chǔ)由SQLite
數(shù)據(jù)庫(kù)支持。 Xcode的Core Data Template
默認(rèn)情況下使用此代碼萌丈,同時(shí)也是項(xiàng)目中使用的store
赞哗。 -
NSXMLStoreType:由
XML
文件支持。 - NSBinaryStoreType:由二進(jìn)制數(shù)據(jù)文件支持辆雾。
- NSInMemoryStoreType:此存儲(chǔ)類(lèi)型會(huì)將數(shù)據(jù)保存到內(nèi)存肪笋,因此不會(huì)持久保存。 這對(duì)于單元測(cè)試很有用度迂,因?yàn)槿绻麘?yīng)用終止藤乙,數(shù)據(jù)就會(huì)消失。
注意:如果您想了解有關(guān)Core Data
中不同存貯的更多信息惭墓,請(qǐng)查看Apple Documentation on Persistent Store Types坛梁。
完成此步驟后,就該編寫(xiě)第一個(gè)測(cè)試了诅妹。
Writing Your First Test
在PandemicReport
中罚勾,ReportService
是用于處理CRUD
邏輯的小類(lèi)毅人。 CRUD
是Create-Read-Update-Delete
(持久存儲(chǔ)最常見(jiàn)的功能)的首字母縮寫(xiě)吭狡。 您將編寫(xiě)單元測(cè)試以驗(yàn)證該功能是否正常運(yùn)行。
要編寫(xiě)測(cè)試丈莺,您將使用Xcode
中的XCTest
框架和XCTestCase
子類(lèi)划煮。
首先在PandemicReportTests
下創(chuàng)建一個(gè)新的Unit Test Case Class
,并將其命名為ReportServiceTests.swift
缔俄。
然后弛秋,在ReportServiceTests.swift
中,在import XCTest
下添加以下代碼:
@testable import PandemicReport
import CoreData
此代碼將應(yīng)用程序和CoreData
框架導(dǎo)入到您的測(cè)試用例中俐载。
接下來(lái)蟹略,將以下兩個(gè)屬性添加到ReportServiceTests
的頂部:
var reportService: ReportService!
var coreDataStack: CoreDataStack!
這些屬性包含對(duì)要測(cè)試的ReportService
和CoreDataStack
的引用。
返回ReportServiceTests.swift
遏佣,刪除以下內(nèi)容:
- 1)
setUpWithError()
- 2)
tearDownWithError()
- 3)
testExample()
- 4)
testPerformanceExample()
您將看到為什么接下來(lái)不需要它們的原因挖炬。
1. The Set Up and Tear Down
您的單元測(cè)試應(yīng)該是孤立且可重復(fù)的(isolated and repeatable)
。 XCTestCase
有兩種方法setUp()
和tearDown()
状婶,用于在每次運(yùn)行之前設(shè)置測(cè)試用例并在之后清除所有測(cè)試數(shù)據(jù)意敛。 由于每個(gè)測(cè)試都從一個(gè)干凈的表盤(pán)開(kāi)始馅巷,因此這些方法有助于使您的測(cè)試孤立且可重復(fù)。
在聲明的屬性下添加以下代碼:
override func setUp() {
super.setUp()
coreDataStack = TestCoreDataStack()
reportService = ReportService(
managedObjectContext: coreDataStack.mainContext,
coreDataStack: coreDataStack)
}
在這里草姻,您將初始化您先前實(shí)現(xiàn)的TestCoreDataStack
以及ReportService
钓猬。 如前所述,TestCoreDataStack
使用內(nèi)存中的存儲(chǔ)(in-memory store)
撩独,并在每次執(zhí)行setUp()
時(shí)進(jìn)行初始化敞曹。 因此,所有創(chuàng)建的PandemicReport
都不會(huì)在每次測(cè)試之間持久存在综膀。
另一方面异雁,tearDown()
會(huì)在每次測(cè)試運(yùn)行后重置數(shù)據(jù)。
返回ReportServiceTests
僧须,添加以下內(nèi)容:
override func tearDown() {
super.tearDown()
reportService = nil
coreDataStack = nil
}
這段代碼將屬性設(shè)置為nil
纲刀,為下一次測(cè)試做準(zhǔn)備。
完成set up
和tear down
后担平,您現(xiàn)在可以專(zhuān)注于測(cè)試報(bào)告的CRUD
示绊。
2. Adding a Report
現(xiàn)在,您將通過(guò)編寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試來(lái)檢驗(yàn)該應(yīng)用程序的現(xiàn)有功能暂论,以驗(yàn)證ReportService
的add(_:numberTested:numberPositive:numberNegative :)
功能面褐。
仍在ReportServiceTests
中,創(chuàng)建一個(gè)新方法:
func testAddReport() {
// 1
let report = reportService.add(
"Death Star",
numberTested: 1000,
numberPositive: 999,
numberNegative: 1)
// 2
XCTAssertNotNil(report, "Report should not be nil")
XCTAssertTrue(report.location == "Death Star")
XCTAssertTrue(report.numberTested == 1000)
XCTAssertTrue(report.numberPositive == 999)
XCTAssertTrue(report.numberNegative == 1)
XCTAssertNotNil(report.id, "id should not be nil")
XCTAssertNotNil(report.dateReported, "dateReported should not be nil")
}
此測(cè)試驗(yàn)證add(_:numberTested:numberPositive:numberNegative :)
創(chuàng)建具有指定值的PandemicReport
取胎。
您添加的代碼:
- 1) 創(chuàng)建一個(gè)
PandemicReport
展哭。 - 2) 斷言輸入值與創(chuàng)建的
PandemicReport
相匹配。
要運(yùn)行此測(cè)試闻蛀,請(qǐng)單擊Product > Test
或按Command + U
作為快捷方式匪傍。 或者,您可以打開(kāi)Test
導(dǎo)航器觉痛,然后選擇PandemicReportsTest
并單擊play
役衡。
該項(xiàng)目將構(gòu)建并運(yùn)行測(cè)試。 您會(huì)看到一個(gè)綠色的選中標(biāo)記薪棒。
恭喜你手蝎! 您已經(jīng)編寫(xiě)了第一個(gè)測(cè)試。
接下來(lái)俐芯,您將學(xué)習(xí)如何測(cè)試異步代碼棵介。
Testing Asynchronous Code
保存數(shù)據(jù)是Core Data
最重要的任務(wù)。 雖然您的測(cè)試很棒吧史,但它不會(huì)測(cè)試數(shù)據(jù)是否保存到持久性存儲(chǔ)中邮辽。 它可以直接運(yùn)行,因?yàn)樵搼?yīng)用程序使用一個(gè)單獨(dú)的隊(duì)列在后臺(tái)保留數(shù)據(jù)。
將數(shù)據(jù)保存在主線(xiàn)程上可能會(huì)阻塞UI
逆巍,使其無(wú)響應(yīng)及塘。 但是,測(cè)試異步代碼要復(fù)雜一些锐极。 具體來(lái)說(shuō)笙僚,由于您不知道后臺(tái)任務(wù)何時(shí)完成,因此XCTAssert
無(wú)法測(cè)試您的數(shù)據(jù)是否保存灵再。
您可以通過(guò)將add(_:numberTested:numberPositive:numberNegative :)
調(diào)用包裝在perform(_ :)
中肋层,在與當(dāng)前上下文關(guān)聯(lián)的線(xiàn)程上執(zhí)行工作來(lái)解決此問(wèn)題。 然后翎迁,您需要將perform(_ :)
與expectation
配對(duì)栋猖,以在保存完成時(shí)通知測(cè)試。
這就是它的樣子汪榔。
1. Testing Save
仍在ReportServiceTests
內(nèi)蒲拉,添加:
func testRootContextIsSavedAfterAddingReport() {
// 1
let derivedContext = coreDataStack.newDerivedContext()
reportService = ReportService(
managedObjectContext: derivedContext,
coreDataStack: coreDataStack)
// 2
expectation(
forNotification: .NSManagedObjectContextDidSave,
object: coreDataStack.mainContext) { _ in
return true
}
// 3
derivedContext.perform {
let report = self.reportService.add(
"Death Star 2",
numberTested: 600,
numberPositive: 599,
numberNegative: 1)
XCTAssertNotNil(report)
}
// 4
waitForExpectations(timeout: 2.0) { error in
XCTAssertNil(error, "Save did not occur")
}
}
這是這樣做的:
- 1) 創(chuàng)建背景上下文和使用該上下文的
ReportService
的新實(shí)例。 - 2) 創(chuàng)建一個(gè)
expectation
痴腌,該期望在Core Data stack
發(fā)送NSManagedObjectContextDidSave
通知事件時(shí)將信號(hào)發(fā)送到測(cè)試用例雌团。 - 3) 它將在
perform(_ :)
塊內(nèi)添加一個(gè)新報(bào)告。 - 4) 測(cè)試等待報(bào)告保存的信號(hào)士聪。 如果等待時(shí)間超過(guò)兩秒锦援,則測(cè)試將失敗。
期望是測(cè)試異步代碼時(shí)的強(qiáng)大工具剥悟,因?yàn)槠谕梢允鼓鷷和4a并等待異步任務(wù)完成灵寺。
注意:要了解有關(guān)以預(yù)期方式測(cè)試異步操作的更多信息,請(qǐng)查看有關(guān)該主題的Apple文檔Apple’s Documentation区岗。
現(xiàn)在略板,運(yùn)行測(cè)試,并在其旁邊看到一個(gè)綠色的選中標(biāo)記躏尉。
編寫(xiě)測(cè)試可以幫助您發(fā)現(xiàn)bugs
并提供有關(guān)函數(shù)行為的文檔蚯根。但是后众,如果您編寫(xiě)了失敗的測(cè)試并且始終通過(guò)了該怎么辦胀糜?現(xiàn)在該做一些TDD了!
Test Driven Development (TDD)
Test Driven Development, or TDD 是一個(gè)開(kāi)發(fā)過(guò)程蒂誉,您可以在生產(chǎn)代碼之前編寫(xiě)測(cè)試教藻。通過(guò)首先編寫(xiě)測(cè)試,您可以確保代碼可測(cè)試并開(kāi)發(fā)為滿(mǎn)足所有要求右锨。
首先括堤,編寫(xiě)最少的代碼以使測(cè)試通過(guò)。然后,您逐步對(duì)功能進(jìn)行微小更改并重復(fù)悄窃。
TDD
的好處之一是您的測(cè)試可以充當(dāng)應(yīng)用程序工作方式的文檔讥电。隨著功能集隨著時(shí)間的推移而擴(kuò)展,您的測(cè)試也將隨之?dāng)U展轧抗,并且據(jù)此擴(kuò)展您的文檔恩敌。
因此,單元測(cè)試是了解應(yīng)用程序特定部分工作方式的好方法横媚。如果您需要復(fù)習(xí)或從另一個(gè)開(kāi)發(fā)人員那里接管代碼庫(kù)纠炮,它們將非常有用。
TDD
的其他好處包括:
- Code Coverage:因?yàn)槟谏a(chǎn)代碼之前編寫(xiě)測(cè)試灯蝴,所以未經(jīng)測(cè)試的代碼的可能性很小恢口。
- Confidence in refactoring:由于代碼覆蓋范圍廣,并且該項(xiàng)目被分解為較小的可測(cè)試單元穷躁,因此可以更輕松地對(duì)代碼庫(kù)進(jìn)行大型重構(gòu)耕肩。
- Focused:您編寫(xiě)的代碼最少,可以通過(guò)測(cè)試问潭,因此您的代碼庫(kù)整潔看疗,冗余程度也較低。
The Red, Green, Refactor Cycle
好的單元測(cè)試是失敗睦授,可重復(fù)两芳,運(yùn)行迅速且易于維護(hù)的。 通過(guò)使用TDD
去枷,可以確保您的測(cè)試值得怖辆。
開(kāi)發(fā)人員通常將TDD
流程描述為red-green-refactor
周期:
- 1) Red:寫(xiě)一個(gè)首先失敗的測(cè)試。
- 2) Green:編寫(xiě)盡可能少的代碼以使其通過(guò)删顶。
- 3) Refactor:修改和優(yōu)化代碼竖螃。
- 4) Repeat:重復(fù)這些步驟,直到您認(rèn)為代碼可以正常工作為止逗余。
注意:如果您想了解有關(guān)iOS中
TDD
的更多信息特咆,請(qǐng)查看我們的教程 Test Driven Development Tutorial for iOS: Getting Started。
有了關(guān)于TDD
的一些理論录粱,現(xiàn)在是時(shí)候?qū)DD付諸實(shí)踐了腻格。
Fetching Reports
為了熟悉TDD,您將編寫(xiě)一個(gè)驗(yàn)證getReports()
的測(cè)試啥繁。 首先菜职,測(cè)試將失敗,但是您將努力確保測(cè)試不會(huì)失敗旗闽。
在ReportServiceTests.swift
中酬核,添加:
func testGetReports() {
//1
let newReport = reportService.add(
"Endor",
numberTested: 30,
numberPositive: 20,
numberNegative: 10)
//2
let getReports = reportService.getReports()
//3
XCTAssertNil(getReports)
//4
XCTAssertEqual(getReports?.isEmpty, true)
//5
XCTAssertTrue(newReport.id != getReports?.first?.id)
}
這段代碼:
- 1) 添加一個(gè)新報(bào)告并將其分配給
newReport
蜜另。 - 2) 獲取當(dāng)前存儲(chǔ)在
Core Data
中的所有報(bào)告,并將它們分配給getReports
嫡意。 - 3) 驗(yàn)證
getReports
的結(jié)果是否為nil
举瑰。 這是一個(gè)失敗的測(cè)試。getReports()
應(yīng)該返回添加的報(bào)告蔬螟。 - 4) 斷言結(jié)果數(shù)組為空嘶居。
- 5) 斷言
newReport.id
不等于getReports
中的第一個(gè)對(duì)象。
運(yùn)行單元測(cè)試促煮。 您會(huì)看到測(cè)試失敗邮屁。
查看失敗測(cè)試中的assert
表達(dá)式的結(jié)果:
該測(cè)試失敗,因?yàn)閳?bào)表服務(wù)返回您添加的報(bào)表菠齿。 斷言與getReports
返回的條件相反佑吝。 如果此測(cè)試確實(shí)通過(guò)了,則說(shuō)明getReports()
無(wú)法正常工作绳匀,或者單元測(cè)試中存在bug
芋忿。
要使測(cè)試從紅色變?yōu)榫G色,請(qǐng)使用以下命令替換斷言:
XCTAssertNotNil(getReports)
XCTAssertTrue(getReports?.count == 1)
XCTAssertTrue(newReport.id == getReports?.first?.id)
這段代碼:
- 1) 檢查
getReports
是否為nil
疾棵。 - 2) 驗(yàn)證報(bào)告數(shù)為
1
戈钢。 - 3) 斷言創(chuàng)建的報(bào)表的
id
,并且報(bào)表數(shù)組中的第一個(gè)結(jié)果匹配是尔。
接下來(lái)殉了,重新運(yùn)行單元測(cè)試。 您會(huì)看到一個(gè)綠色的對(duì)號(hào)拟枚。
成功薪铜! 您已將失敗的測(cè)試變成綠色,確認(rèn)代碼和測(cè)試有效且不是錯(cuò)誤恩溅。
接著隔箍,下一個(gè)。
Updating a Report
現(xiàn)在脚乡,編寫(xiě)一個(gè)測(cè)試來(lái)驗(yàn)證update(_ :)
的行為是否符合預(yù)期蜒滩。 添加以下測(cè)試方法:
func testUpdateReport() {
//1
let newReport = reportService.add(
"Snow Planet",
numberTested: 0,
numberPositive: 0,
numberNegative: 0)
//2
newReport.numberTested = 30
newReport.numberPositive = 10
newReport.numberNegative = 20
newReport.location = "Hoth"
//3
let updatedReport = reportService.update(newReport)
//4
XCTAssertFalse(newReport.id == updatedReport.id)
//5
XCTAssertFalse(updatedReport.numberTested == 30)
XCTAssertFalse(updatedReport.numberPositive == 10)
XCTAssertFalse(updatedReport.numberNegative == 20)
XCTAssertFalse(updatedReport.location == "Hoth")
}
這段代碼:
- 1) 創(chuàng)建一個(gè)新報(bào)告并將其分配給
newReport
。 - 2) 將
newReport
的當(dāng)前屬性更改為新值奶稠。 - 3) 調(diào)用
update:
保存所做的更改俯艰,并將更新后的值分配給updatedReport
。 - 4) 斷言
newReport.id
與updatedReport.id
不匹配窒典。 - 5) 確保
updatedReport
屬性不等于分配給newReport
的值蟆炊。
運(yùn)行單元測(cè)試。 您會(huì)看到測(cè)試失敗瀑志。
查看單元測(cè)試,注意五個(gè)斷言都失敗了。
他們失敗了劈猪,因?yàn)?code>newReport屬性等于updatedReport
的屬性昧甘。 換句話(huà)說(shuō),您希望測(cè)試在這里失敗战得。
將testUpdateReport()
中的斷言更新為:
XCTAssertTrue(newReport.id == updatedReport.id)
XCTAssertTrue(updatedReport.numberTested == 30)
XCTAssertTrue(updatedReport.numberPositive == 10)
XCTAssertTrue(updatedReport.numberNegative == 20)
XCTAssertTrue(updatedReport.location == "Hoth")
更新的代碼測(cè)試newReport.id
是否等于updatedReport.id
充边,以及updatedReport
屬性是否等于分配給newReport
的屬性。
重新運(yùn)行測(cè)試常侦,看看它們現(xiàn)在通過(guò)了浇冰。
到目前為止,您所做的工作比根本沒(méi)有測(cè)試要好得多聋亡,但是您仍然沒(méi)有真正遵循TDD
的做法肘习。 為此,您必須在編寫(xiě)應(yīng)用程序中的實(shí)際功能之前編寫(xiě)測(cè)試坡倔。
Extending Functionality
到目前為止漂佩,您已經(jīng)添加了測(cè)試以支持該應(yīng)用程序的現(xiàn)有功能。 現(xiàn)在罪塔,您將通過(guò)擴(kuò)展服務(wù)的功能來(lái)將TDD
技能提升到一個(gè)新的水平投蝉。
為此,您將添加刪除記錄的功能征堪。 由于這次您將遵循真正的TDD
練習(xí)瘩缆,因此必須在生產(chǎn)代碼之前編寫(xiě)測(cè)試。
構(gòu)建并運(yùn)行并添加報(bào)告佃蚜。 添加報(bào)告后咳榜,可以通過(guò)在單元格上輕掃并點(diǎn)擊Delete
來(lái)將其刪除。
但是您僅從ViewController.swift
中的reports
實(shí)例中刪除了該報(bào)表爽锥。 該報(bào)告仍存在于Core Data Store
中涌韩,并且在您再次構(gòu)建并運(yùn)行時(shí)將返回。
要進(jìn)行檢查氯夷,請(qǐng)重新構(gòu)建并運(yùn)行臣樱。
該報(bào)告仍然存在,因?yàn)?code>ReportService.swift沒(méi)有刪除功能腮考。 接下來(lái)雇毫,您將添加此內(nèi)容。
但是首先踩蔚,您必須編寫(xiě)一個(gè)測(cè)試棚放,該測(cè)試具有刪除功能所期望的所有功能。
在ReportServiceTests.swift
中馅闽,添加:
func testDeleteReport() {
//1
let newReport = reportService.add(
"Starkiller Base",
numberTested: 100,
numberPositive: 80,
numberNegative: 20)
//2
var fetchReports = reportService.getReports()
XCTAssertTrue(fetchReports?.count == 1)
XCTAssertTrue(newReport.id == fetchReports?.first?.id)
//3
reportService.delete(newReport)
//4
fetchReports = reportService.getReports()
//5
XCTAssertTrue(fetchReports?.isEmpty ?? false)
}
這段代碼:
- 1) 添加一個(gè)新的報(bào)告飘蚯。
- 2) 從
store
中獲取包含報(bào)告的所有報(bào)告馍迄。 - 3) 在
reportService
上調(diào)用delete
刪除報(bào)告。 由于此方法尚不存在局骤,因此將失敗攀圈。 - 4) 再次從
store
獲取所有報(bào)告。 - 5) 聲明報(bào)表數(shù)組為空峦甩。
添加此代碼后赘来,您會(huì)看到編譯錯(cuò)誤。 目前凯傲,ReportService.swift
沒(méi)有實(shí)現(xiàn)delete(_ :)
犬辰。 接下來(lái),您將添加它冰单。
1. Deleting a Report
現(xiàn)在幌缝,打開(kāi)ReportService.swift
并在類(lèi)末尾添加以下代碼:
public func delete(_ report: PandemicReport) {
// TODO: Delete record from CoreData
}
在這里,您添加了一個(gè)空的聲明球凰。 記住狮腿,TDD
規(guī)則之一是編寫(xiě)足夠的代碼以使測(cè)試通過(guò)。
您解決了編譯錯(cuò)誤呕诉。 重新運(yùn)行測(cè)試缘厢,您將看到一個(gè)失敗的測(cè)試。
現(xiàn)在測(cè)試失敗了甩挫,因?yàn)槲磸?code>store中刪除該記錄贴硫。 當(dāng)前,ReportService.swift
中的delete
方法具有空主體伊者,因此實(shí)際上沒(méi)有任何內(nèi)容被刪除英遭。 接下來(lái),您將對(duì)其進(jìn)行修復(fù)亦渗。
返回ReportService.swift
挖诸,添加以下實(shí)現(xiàn)到delete(_:)
:
//1
managedObjectContext.delete(report)
//2
coreDataStack.saveContext(managedObjectContext)
代碼:
- 1) 從持久性存儲(chǔ)
(persistent store)
中刪除報(bào)告。 - 2) 將更改保存在當(dāng)前上下文中法精。
添加該代碼后多律,重新運(yùn)行單元測(cè)試。 您會(huì)看到綠色的選中標(biāo)記搂蜓。
做得好狼荞! 您已使用TDD
周期向應(yīng)用程序添加了刪除功能。
完成后帮碰,轉(zhuǎn)到ViewController.swift
并將tableView(_:commit:forRowAt :)
替換為以下內(nèi)容:
func tableView(
_ tableView: UITableView,
commit editingStyle: UITableViewCell.EditingStyle,
forRowAt indexPath: IndexPath
) {
guard
let report = reports?[indexPath.row],
editingStyle == .delete
else {
return
}
reports?.remove(at: indexPath.row)
//1
reportService.delete(report)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
在這里相味,您將delete(_ :)
從報(bào)表數(shù)組中刪除后,將其調(diào)用殉挽。 現(xiàn)在丰涉,如果您滑動(dòng)并刪除拓巧,該報(bào)告將從SQLite
支持的數(shù)據(jù)庫(kù)中刪除。
構(gòu)建并運(yùn)行以進(jìn)行檢查昔搂。
很好地將測(cè)試引入具有Core Data
的項(xiàng)目玲销。
在本教程中输拇,您學(xué)習(xí)了如何:
- 使用內(nèi)存存儲(chǔ)
(in-memory store)
編寫(xiě)可測(cè)試的Core Data stack
摘符。 - 為現(xiàn)有功能和新功能編寫(xiě)測(cè)試。
- 測(cè)試異步代碼策吠。
- 應(yīng)用
Test Driven Development (TDD)
方法逛裤。
如果遇到挑戰(zhàn),請(qǐng)嘗試編寫(xiě)一個(gè)或多個(gè)測(cè)試猴抹,以防止為numberTested带族,numberPositive和numberNegative添加非負(fù)數(shù)。
如果您喜歡本教程蟀给,請(qǐng)查看iOS Test-Driven Development by Tutorials蝙砌。您將通過(guò)先編寫(xiě)測(cè)試或?qū)y(cè)試添加到已編寫(xiě)的應(yīng)用中來(lái)深入研究如何編寫(xiě)可維護(hù)和可持續(xù)的應(yīng)用。您還應(yīng)該查看 Test Driven Development Tutorial for iOS: Getting Started跋理。
如果您想了解更多有關(guān)Core Data的信息择克,請(qǐng)查看 Getting Started with Core Data Tutorial。
后記
本篇主要講述了基于
Unit Testing
的Core Data
測(cè)試前普,感興趣的給個(gè)贊或者關(guān)注~~~