#-3 Xcode Unit Test
目錄
⦿ 測試APP
⦿ Unit Test
⦿ 建立一個簡單的頁面操作
⦿ textExample
⦿ XCTAssert
⦿ 其他Assert
⦿ 從頭加入測試
⦿ 再來個簡易示例
⦿ testPerformanceExample
測試APP
我們在 Xcode 寫完一個 APP 後會怎麼去測試它呢?Build。
在採用 SwiftUI 編寫 APP 時,我們通常會一邊觀察 Preview 的變化,一邊寫 Code,寫完後,我們還是需要 Build,尤其是在實機上。
雖然在 UIKit 也有辦法去觀察 Preview,但不是今天的重點。
繼續閱讀|回目錄
Unit Test
一個完整的 APP 並非簡單建置幾個頁面就完成,所以我們需要 Build ,但與 Build 不同的,若我們只想測試其中一個頁面的功能時,應該怎麼辦呢?
或者對於重複的測試操作已感厭煩的我們,又該怎麼辦呢?
難道要修改程式碼,Build,再修改程式碼,再 Build⋯⋯(雖然 lldb 的指令可以解決這種繁瑣,不過仍不在此次討論範圍內)
這時候,我們可以使用 Xcode 內建的 Unit Test 功能。
繼續閱讀|回目錄
建立一個簡單的頁面操作
首先,我們在 Main.storyboard 中拉了一個 button、一個 textfield、一個 label,按下 button 計算了內部編碼給予的數值(相加)。
由於 storyboard 預設與 ViewController.swift 繫結,所以這三個 UI元件 自然也要在 ViewController 裡與 UI class 繫結,才能在 viewDidLoad( ) 控制。
接著看到按下 button 的 action 裡用到了 lblText.text,而 lblText 從屬於 UITextField 這個 class,即是上方繫結的 textField,按下 button 後,text 會改變。
裡面宣告的 obj 是 Sample 這個 class,有兩個參數 12 跟 8,由此推斷 Sample 有個 add( ) 方法,後面才有 obj 用 dot syntax 呼叫了 add( ),如下:
繼續閱讀|回目錄
textExample
寫完基本頁面操作後,接著,我們跳到 Unit Test 頁面
這邊分別看到兩個覆寫方法跟兩個方法,setUpWithError( )、tearDownWithError( )、testExample( )、testPerformanceExample( ) 作用如下:
- setUpWithError( ):測試前要執行的,例如你宣告了 sop = URLSession
- tearDownWithError( ):測試後要執行的,例如將 sop = nil
- testExample( ):你要測試的方法,它的 prefix 必須是 test 開頭,在單元測試裡我們就寫了一個 testAdd( ) 方法。
- testPerformanceExample( ):效能測試,當我們以快捷 ⌘ + u 去做圖中的三個測試( testAdd、testExample、testPerformanceExample ),可看到左邊會有測試成功 ✅ 的圖案,整體 class 左邊也有一個測試成功 ✅ 的圖案,而 testExample( ) 沒東西暫時不用管它,但測試效能在 measure 的地方出現了灰色警示,提示執行時間沒有基準可參照。
繼續閱讀|回目錄
XCTAssert
接著看 testAdd( ) 裡面的 XCTAssert。
XCTAssert 是用來判斷有沒有錯誤,透過官方文件,XCTAssert 的內容是 Asserts that expression is true.(假定表達式正確)
如果 expression 為 true,就不會有後面的錯誤提示,即是 message;反之,false 就會在 debug 面板上顯示出 message。
其他Assert
我們再看到 XCTAssertTrue 跟 XCTAssertFalse。
XCTAssertTrue 是你的 expression 需屬實,XCTAssertFalse 是你的 expression 需屬虛,如此才會是正確操作。
另有一個 XCTFail,XCTFail 是無條件失敗,直接給出錯誤訊息,前面並沒有將 XCTFail 寫入測試中,因為這樣無論如何都不會有 ✅ 。
然而,在 Xcode 裡面,為什麼需要有無條件失敗這一項,有的時候可以把它看成中斷點,有的時候則是希望提前發生錯誤,即是在錯誤之前的錯誤,這都是為了抓出錯誤的原因。
剩下的 XCTAssert:
- XCTAssertNil、XCTAssertNotNil,Nil 必須給它 Nil,無值,NotNil 必須給它非 Nil,有值,同樣在錯誤時才拋出錯誤訊息。
- XCTAssertEqual、XCTAssertNotEqual,Equal 是第一、第二參數必須相等,NotEqual 是第一、第二參數必須不相等。例如在 Equal 中,val, 100 表示 val == 100,同樣在錯誤時才拋出錯誤訊息。
測試時,一定得整頁程式碼由上而下,逐一測試嗎?
不,如果你有多個方法要個別測試,只要按下該方法旁邊的 start,比方說我現在只想測試 testAdd( ),就按下 test 旁邊的 start,此時測試正確的話就會出現 ✅,測試錯誤的話就出現 ❌。
注意!
雖然方法可以個別測試,但最上層的 Unit Test 的整體測試,必須在內部所有方法測試正確後才會顯示正確(即出現 ✅)。
繼續閱讀|回目錄
從頭加入測試
接著回到建立專案( Project )之初,下方就會問你要不要加入 Test,早先版本的 Xcode 會分別問你是要加入 Unit Test 還是 UI Test,會有兩個格子需要分別打勾,如今只剩下一個。
順帶複習一下,建立專案時,如果 Interface 選擇 Storyboard,Life Cycle 則只有 UIKit App Delegate,Language 處可選 Swift 或 OC;若 Interface 選擇 SwiftUI,Life Cycle 則多了一個 SwiftUI,Language 只剩 Swift 可選。
簡而言之,要用 UIKit 來編寫的話使用 Storyboard;要用 SwiftUI 來編寫的話使用 SwiftUI framework(SwiftUI 框架),Language 只能是 Swift。
接著,如果當初建立專案時忘了加入 Tests,在專案底下,我們可以從 File => New => Target 依序加入以下:
當然,我們也可以在 Xcode 的 Navigator(左邊欄位)右鍵 New File,加入Cocoa Touch Class,Subclass of XCTestCase 來加入另外的測試。
不過這個另寫的測試,在測試前,別忘了在程式碼最上方 import Model,像 @testable import TestTest(你另寫的測試檔案的名稱),至此,Unit Test(單元測試)都是在呼叫方法,並測試方法是否正確,沒有與 UI 連動。
繼續閱讀|回目錄
再來個簡易示例
我們再創建兩個數字的 加減乘除 共四個方法,分別測試是否正確。
也可以在 ViewController 裡寫方法,而不是新的 class 裡,於是,單元測試裡就要稍微調整,先寫方法,從加法開始
接著在 setUpWithError( ) 裡初始化
這段 Code 相當簡單,就是實例化一個 vc,並轉型為 ViewController,於是這個 vc 就可以再去呼叫前面寫在 ViewController 裡的 plus。
再把它寫進測試方法裡,如下:
我們看到第一個方法的結果正確,所以出現 ✅;但第二個方法的結果錯誤,所以出現 ❌,後面也看到錯誤訊息:Result is not nil.
再來,我們還想測試在按下 button 後程式碼是否正確,如下:
從 vc 去呼叫前面的 btnRun,此時 sender 為 self,而在 XCTAssert 裡,從 vc 去找到 lblText 的顯示結果,由於 btnRun 是 12 + 8,結果不為 30。
不過,此時卻回報錯誤如下:
奇怪,vc 已經初始化了,從 vc 呼叫方法,檢查 label 是否正確,這裡卻說 label 是 nil。
注意!
這不是 Unit Test 的錯誤。
上網查後才發現,原來還必須初始化 view,而蘋果官方不建議在 Unit Test 裡 loadView( ),因為可能導致 memory leaks。
我們回到 setUpWithError( ) 加上一行:_ = vc.view
,表示所有的 view 都被初始化,這時候 label 就不會是 While implicitly unwrapping an Optional value(隱性解包)but found nil (卻無值)了。
補充:使用 _(底線)做為變數名稱,原因在於當你不會使用這個變數,就不要佔用名稱的空間。
那麼 _ = vc.view
就是告訴程式存取與觸發視圖。
如此便可測試了,如下:
這下便跑出正確的單元測試錯誤,是不是很棒呀?
繼續閱讀|回目錄
testPerformanceExample
最後來講效能測試,testPerformanceExample( )。
我們試著加入一段測試,呼叫 add( ),讓 1 ~ 5000000 去加 8,幾番測試後,設定 baseline(效能基準時間)。
可以看到 baseline 設定為 1.98 s,接著跑十次做平均,平均 1.99 s,比baseline 還要差一個百分比( 1% worse)。
另外還有 Code Coverage,可以看到各個方法執行時的覆蓋率先不研究了,因為它似乎有點謎。
如此,單元測試的部分便完成了,感謝您的閱讀。
繼續閱讀|回目錄