Xcode7 中用 Swift 做單元測(cè)試

作者:Maxime Defauw,原文鏈接,原文日期:2016-02-29
譯者:ray;校對(duì):numbbbbb;定稿:way

每個(gè) iOS 程序員都要時(shí)不時(shí)的為他們的 app 做 debug。除非你是那種超級(jí)大牛,否則你肯定體驗(yàn)過查了無數(shù)個(gè)小時(shí)的 bug 最后才發(fā)現(xiàn)那僅僅是個(gè)簡(jiǎn)單的語法錯(cuò)誤時(shí)那種油然而生的絕望感?;蛘吒悖耗愀揪蜎]發(fā)現(xiàn)那些 bug。無論你是編程新手,還是開發(fā)過很多 app 的老司機(jī),例行的寫寫單元測(cè)試會(huì)讓你的代碼更可靠,更安全,更容易 debug!

你很走運(yùn),Xcode 7 和 Swift 支持單元測(cè)試。盡管單元測(cè)試不保證(有了它你就會(huì)寫出)絕對(duì)沒有 bug 的 app,它還是一種能讓你驗(yàn)證每段代碼是否如期工作,并讓 debug 過程更加便利。

正如其名,在單元測(cè)試中你要為某段代碼單元?jiǎng)?chuàng)建一些小規(guī)模的、針對(duì)其某個(gè)特性的測(cè)試,然后確保每個(gè)代碼單元都能通過這些測(cè)試。如果通過的話,它的旁邊會(huì)出現(xiàn)一個(gè)綠色小標(biāo)志,而如果因故測(cè)試不通過, Xcode 會(huì)把該測(cè)試標(biāo)記為 "failed"。這就提示你去查看代碼,找出失敗原因。

演示項(xiàng)目概覽

首先下載這個(gè)我為你準(zhǔn)備的 starting project。一個(gè)短小精悍的 app:它會(huì)對(duì)一個(gè)給定的數(shù)字和百分比做一個(gè)乘法計(jì)算。(比如80的10%是8。)

這個(gè) PercentageCalculator 項(xiàng)目非常簡(jiǎn)單。你唯一需要關(guān)注的就是 ViewController.swift 這個(gè)文件。里面的代碼都標(biāo)記了注釋,很容易理解。

有 5 個(gè) IBOutlets:每一個(gè)都對(duì)應(yīng)了屏幕上一個(gè) UIElement,除 title(標(biāo)題)之外,還有 2 個(gè) slider 對(duì)應(yīng) 2 個(gè) IBActions。每個(gè) IBAction 的方法名都精確描述了其用途及將要執(zhí)行的操作。當(dāng)一個(gè) slider 值改變時(shí),其對(duì)應(yīng)著的百分比或數(shù)字的值也會(huì)隨之改變。

還有兩個(gè)簡(jiǎn)單的函數(shù) “updateLabels()” 和 “percentage()” 做了符合期待的事情:當(dāng)一個(gè) slider 改變時(shí)第一個(gè)函數(shù)更新 label,第二個(gè)函數(shù)獲取兩個(gè)浮點(diǎn)數(shù)并返回百分比的計(jì)算結(jié)果。

在模擬器中運(yùn)行 app。剛開始一切看起來都很正常。但當(dāng)你開始改變數(shù)字時(shí)就會(huì)發(fā)現(xiàn)計(jì)算結(jié)果有問題。為找到 bug,我們將代碼分割成不同的單元,然后分別做測(cè)試,看看每個(gè)是否都如期運(yùn)行。這不會(huì)解決 bug,但能縮小你的查找范圍。

我創(chuàng)建項(xiàng)目的時(shí)候,默認(rèn)情況下會(huì)勾選創(chuàng)建一個(gè) test 文件的選項(xiàng)(如果你想要手動(dòng)加一個(gè)的話,在 iOS Source 下面選擇 select File > New > File > Unit Test Case Class)。我們的例子中 test 文件已經(jīng)被 Xcode 自動(dòng)創(chuàng)建出來,可以在項(xiàng)目導(dǎo)航欄中 “PercentageCalculatorTests” 文件夾中找到它。

PercentageCalculatorTests.swift 文件中,PercentageCalculatorTests 類里面已經(jīng)為我們創(chuàng)建好了 4 個(gè)方法。其中 2 個(gè)是測(cè)試方法(test methods)的例子,你可以刪掉它們(它倆都以 test 關(guān)鍵字開頭,并且它們左邊的豎條中都有個(gè)方塊形圖標(biāo),名字也都以 “...Example” 結(jié)尾,所以你可以通過這些辨識(shí)出來它們是測(cè)試方法)。另外兩個(gè)方法,setUp()tearDown() 是特殊的樣板方法(boilerplate methods),它們分別在每個(gè)測(cè)試方法被執(zhí)行之前,和每個(gè)測(cè)試方法被執(zhí)行之后被執(zhí)行。

開始寫單元測(cè)試吧

現(xiàn)在是時(shí)候?qū)懩愕牡谝粋€(gè)單元測(cè)試函數(shù)了!本教程我們只測(cè)試 ViewController 類,需要在 PercentageCalculatorTests 中添加一個(gè)它的實(shí)例。

class PercentageCalculatorTests: XCTestCase {
    var vc: ViewController!
    
    override func setUp() {
        super.setUp()
        // 這里寫setup的代碼。本class里每個(gè)測(cè)試函數(shù)被調(diào)用之前該方法都會(huì)被先調(diào)用。
    }
    
    override func tearDown() {
        // 這里寫teardown的代碼。本class里每個(gè)測(cè)試函數(shù)被調(diào)用之后該方法都會(huì)被調(diào)用。
        super.tearDown()
    }
    
}

PercentageCalculatorTests 是一個(gè) XCTestCase 的子類,后者被打包在 XCTest 框架中。每一個(gè) XCTestCase 子類的實(shí)例都負(fù)責(zé)對(duì)你項(xiàng)目的某個(gè)特定部分做測(cè)試,比如對(duì)一個(gè)特性做測(cè)試。

在 setup 方法中實(shí)例化一個(gè) vc。這樣對(duì)每一個(gè)測(cè)試方法你都會(huì)得到一個(gè)“全新的” ViewController 實(shí)例,因?yàn)樵诿總€(gè)測(cè)試方法執(zhí)行前 setUp() 都會(huì)被調(diào)用一次。把 setUp() 方法修改如下:

override func setUp() {
    super.setUp()
 
    let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
    vc = storyboard.instantiateInitialViewController() as! ViewController
}

現(xiàn)在你應(yīng)該記得所有的測(cè)試方法的名字都要以 test 關(guān)鍵字開頭,否則 Xcode 不會(huì)識(shí)別。添加一個(gè)新的 testPercentageCalculator() 測(cè)試方法,來驗(yàn)證一下 ViewController 中的 percentage() 工作是否正常。

func testPercentageCalculator() {
}

單元測(cè)試中你要去檢查某段代碼是否如你所愿的那樣工作。待測(cè)試的代碼段一般都只有幾行,典型情況是你只需要測(cè)試一個(gè)方法或者一個(gè)函數(shù)。單元測(cè)試是這樣去做的:你給某個(gè)代碼單元一個(gè)輸入值,讓這個(gè)值過一遍這段代碼,然后檢查一下輸出的值是否和預(yù)期的一樣。

與“我們期望的那個(gè)值”做比較的這部分由 XCTAssert 函數(shù)來處理。最簡(jiǎn)單的 XCTAssert 函數(shù)是XCTAssert(expression: BooleanType)。這個(gè)函數(shù)要求一個(gè)布爾表達(dá)式(類似于 5>3,8.90 == 8.90或者 true 這種),隨后如果表達(dá)式為真則讓測(cè)試通過,否則認(rèn)為測(cè)試失敗。

嘗試一下!首先給 testPercentageCalculator() 方法加添加下面一行。然后把光標(biāo)移到方法名左邊側(cè)欄的那個(gè)方塊圖標(biāo)上,停下光標(biāo)之后方塊圖標(biāo)變成了一個(gè)執(zhí)行光標(biāo),點(diǎn)擊一下就開始了測(cè)試。

func testPercentageCalculator() {
        XCTAssert(true)
}

如果一切順利,則測(cè)試通過,方法左邊會(huì)出現(xiàn)一個(gè)綠色檢測(cè)標(biāo)。

驗(yàn)證百分比計(jì)算

現(xiàn)在來真的:測(cè)試 percentage() 方法!用 ViewController 的一個(gè)實(shí)例 - vc 屬性來調(diào)用這個(gè)方法。給這個(gè)方法兩個(gè)浮點(diǎn)數(shù),比如 50 和 50,然后把結(jié)果存儲(chǔ)到常量 p 中。這個(gè)例子中 p 應(yīng)該是 25(50 的 50% 是 25)。然后用 XCTAssert(p == 25) 檢測(cè)一下是不是這樣,執(zhí)行測(cè)試方法。把 testPercentageCalculator() 改成這樣:

func testPercentageCalculator() {
        // 應(yīng)該是25
        let p = vc.percentage(50, 50)
        XCTAssert(p == 25)
}

測(cè)試成功了,這意味著 ViewControllerpercentage() 函數(shù)工作正常,我們應(yīng)該在其他的地方繼續(xù)尋找 bug。也許 bug 在 updateLabels() 里面?

驗(yàn)證Labels

現(xiàn)在添加一個(gè)新的測(cè)試方法 testLabelValuesShowedProperly() 來驗(yàn)證一下 label 能不能正確的顯示 text。和之前一樣,調(diào)用 ViewController 的一個(gè)方法 - 這回是 updateLabels() - 然后看看每個(gè)標(biāo)簽的 text 屬性和我們期望的那個(gè) text 是否相同。

注意到你要給 XCTAssert 函數(shù)傳一個(gè)新的參數(shù):一個(gè) string 類型的消息。這對(duì)我們這次要對(duì)多個(gè)值做檢查(調(diào)用三次 XCTAssert )來完成測(cè)試而言就會(huì)很方便。如果測(cè)試失敗,這條消息就會(huì)指名我們具體是哪里錯(cuò)了。

func testLabelValuesShowedProperly() {
        vc.updateLabels(Float(80.0), Float(50.0), Float(40.0))
        
        // labels應(yīng)該顯示80, 50 and 40
        XCTAssert(vc.numberLabel.text == "80.0", "numberLabel doesn't show the right text")
        XCTAssert(vc.percentageLabel.text == "50.0%", "percentageLabel doesn't show the right text")
        XCTAssert(vc.resultLabel.text == "40.0", "resultLabel doesn't show the right text")
}

你嘗試執(zhí)行這個(gè)測(cè)試方法時(shí),會(huì)收到編譯器的錯(cuò)誤提示:numberLabel,percentageLabelresultsLabelnil。怎么回事呢?

我是在 storyboard 文件中創(chuàng)建了這些 labels 的,因此只有當(dāng) view 被加載之后(loaded)它們才會(huì)被初始化,然而由于對(duì)單元測(cè)試來說 loadView() 方法不會(huì)被觸發(fā),所以這些 labels 沒有被創(chuàng)建,只能是 nil。一種可能的方法是通過調(diào)用 vc.loadView() 來解決,但是 Apple 在它的文檔中并不推薦你這么做,因?yàn)楫?dāng)已經(jīng)被加載的對(duì)象又被加載一次的話可能會(huì)引起內(nèi)存泄露。

正確的方法是你應(yīng)該先訪問一下 vcview 這個(gè)屬性,這會(huì)讓 vc 反過來觸發(fā)所有相應(yīng)的方法,不僅僅包括 loadView()。把 testLabelValuesShowedProperly() 改成這樣:

func testLabelValuesShowedProperly() {
        let _ = vc.view
        vc.updateLabels(Float(80.0), Float(50.0), Float(40.0))
        
        // labels應(yīng)該顯示80, 50 and 40
        XCTAssert(vc.numberLabel.text == "80.0", "numberLabel doesn't show the right text")
        XCTAssert(vc.percentageLabel.text == "50.0%", "percentageLabel doesn't show the right text")
        XCTAssert(vc.resultLabel.text == "40.0", "resultLabel doesn't show the right text")
}

注意到下劃線(_)忽略了常量的名字。因?yàn)槲覀儗?shí)際上并不需要用到這個(gè) view。加下劃線就是告訴編譯器“你假裝訪問一下這個(gè) view,把相應(yīng)的方法觸發(fā)就行?!?/p>

執(zhí)行測(cè)試。(如果想一并執(zhí)行我們test類的所有測(cè)試,你還可以點(diǎn)擊 “class PercentageCalculatorTests” 旁邊的那個(gè)方塊)。

我們來修Bug

如你所見,測(cè)試失敗了!我們給 XCTAssert 方法傳入的錯(cuò)誤細(xì)節(jié)消息幫助我們快速識(shí)別出引起 bug 的可能原因。這次測(cè)試告訴我們 resultsLabel 沒有顯示出正確的文本,所以我們進(jìn)到 ViewController 里看看對(duì)這些 label 的 text 值是在那里被設(shè)置的。仔細(xì)看了 ViewController.swiftupdateLabels() 代碼之后,我們發(fā)現(xiàn)了 bug 的原因:

self.resultLabel.text = "\(rV + 10)"

應(yīng)該是:

self.resultLabel.text = "\(rV)"

更新代碼之后再運(yùn)行一次測(cè)試,一切都應(yīng)該正常了!

結(jié)論

本篇教程中你學(xué)到了 Xcode 中的單元測(cè)試的相關(guān)內(nèi)容,以及它怎樣能夠幫你找到代碼中的 bug。除了預(yù)防 bug 之外,單元測(cè)試還可以用來做性能測(cè)試和異步測(cè)試。還可能讓你感興趣的是UI測(cè)試,你可以錄制下你在 app 上做出的動(dòng)作來測(cè)試你的 app 在實(shí)際使用情景下是如何表現(xiàn)的。如果聽起來覺得感興趣,那一定要看看這個(gè)講 UI 測(cè)試的 WWDC視頻。

項(xiàng)目的最終版本可以在 Github上下載。

如果你有關(guān)于 UI 測(cè)試的任何問題,或者學(xué)習(xí)本教程中遇到了困難,請(qǐng)?jiān)谠u(píng)論中點(diǎn)我!

作者介紹:Maxime Defauw 是一個(gè)有經(jīng)驗(yàn)的程序員,在 App Store 和 Google Play store 上發(fā)布過多個(gè) app。他今年 16 歲,居住在比利時(shí)。最近他在 San Francisco 舉行的 WWDC15 上獲得了 Apple 的獎(jiǎng)學(xué)金。Max 熟練掌握 Objective-C,C,C#,現(xiàn)在是 Swift。不碼代碼的時(shí)候他一般在曲棍球場(chǎng)或者高爾夫球場(chǎng)上。在 Twitter 上 @MaximeDefauw 粉他。

本文由 SwiftGG 翻譯組翻譯,已經(jīng)獲得作者翻譯授權(quán),最新文章請(qǐng)?jiān)L問 http://swift.gg。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容