Unit Test , UI Test , Jenkins (C.I.) 教學 — iOS

Jerry Wang
21 min readJun 6, 2017

--

這次的主題為在軟體開發流程中很常被忽視的測試(Unit test、UI Test)及替各位開發者節省更多時間的持續整合(Continuous-Integration),及在iOS的開發流程中如何實現。

本文並不會有過多程式碼的出現及教學如何寫測試程式,主要著重在工具的使用及觀念的介紹,並帶出必須了解的關鍵字讓大家去研究,畢竟在測試的領域已有很多前人撰寫的好文章,為節省時間,本文就不著墨過多。

首先,為何需要測試?

誰也不敢保證自己寫的程式碼百分之百沒bug、或者在開發環境沒事,但當多位使用者湧入時bug立現、或甚至改動某部分的程式碼,卻造成另一部分的bug產生。

以上這些問題,相信大家都有遇過。此時若寫好了測試程式,不論是改動程式碼,或是突然有bug產生。因為測試程式的結果非如預期,開發者可第一時間知道哪邊出了問題,減少了開發過程中造成的失誤,且對自己交付的程式碼更有信心。

還有一項個人認為最重要的優點:由測試程式可略知此程式碼的主要功能為何,做了哪些事,對於剛接手前人的程式碼十分的方便。

推薦閱讀:

官方guideline必讀,這就不用多說了。

大推!最完整的iOS測試部落格,請每一篇一定都要讀過!

Ray的兩篇教學也值得一讀,是iOS測試的入門教學。

(1)Unit Test

Unit Test的測試觀念不只是在App上,Web、甚至是其他的軟體開發也會使用到。

Unit 指的是程式中最小的邏輯單元,小至一個函式,甚至是一個class,都可為Unit,拿來做測試。

測試經過每個Unit後得到的結果,是否如我們所預期,若結果非我們預期的答案,則表示測試失敗。

根據測試的時間點,主要分成TDD(Test — Driven Development)和BDD(Behavior — Driven Development)兩種測試開發流程。

簡單來說,TDD為先寫測試程式,再進行開發。

而BDD為將使用者需求及行為轉換成程式碼,讓使用者、開發者一起了解需求,避免中間的傳達造成的需求失真,最後再利用使用者行為,作為執行測試的方法。

在Unit Test中常用的方法為利用Mock Object、Stub Object、Fake Object進行測試。

而在撰寫測試程式時,常使用的手法為Inject Dependency、及找出SUT(system under test)

下方網站用swift解釋Stub、Mock、Fake三者的差別,解釋得很好:

Stubs

“Fakes a response to method calls of an object”

Mock

“Let you check if a method call is performed or if a property is set”

舉例來說,

下方為一個mock object的範例,用來測試UserDefault是否確實被呼叫,若有被呼叫set函數,則將其內的totalCountAfterChanged變數的結果打印出來為true。

下方為一個Stub的範例,假設後端尚未建立,此時先撰寫Stub object來模擬呼叫api的情況,在裡面寫定一個假的api並傳入程式內,測試看是否程式執行時能得到相同的api。

假設開發者開發的程式如下,正常的執行時,使用官方的UserDefault class及正確的ItunesAPI class。

當要進行測試時,將SearchMusicViewController內的itunesAPI及userDefault兩個屬性傳入上方的mock和stub object,也就是MockUserDefaults claa和StubItunesAPI class的instance,此手法即Inject Dependency。

其他Inject Dependency常用的情況還有當正常開發建立的object,在測試程式中初始化時,傳入經過設計的mock 或stub object,來測試得到的結果為何。

下方的MockURLSession class即為mock object,其中當我們將nextData屬性指定為我們設定好的Data,模擬整個網路請求流程,此即Fake。

Fake最好懂,將假的或給定好的資料塞入程式碼內。

而mock和stub的差別在於,mock無法知道結果會如何,欲測試的內容為是否呼叫了某個函數,再將結果打印出來。

而stub為內部給定了固定的最後結果,若成功的話,會得到我們給定的結果。

(2)UI Test

UI Test用來測試當使用者使用應用程式時,各UI元件的狀態和屬性,是否有如開發者所預期的期望。

(3)Testing in Xcode and iOS with Swift

此欄位稱為”test navigator”,在xcode中,點選下方的加號,可選擇新增Unit / UI Test的target或class。

在下方例子中,共有4個Test Bundle,第1個Test Bundle內有3個Test Class,第1個Test Class內有4個Test Method。點選“New Unit / UI Test Target”會新增Test Bundle。

從Test Bundle的Bundle這個字,應該可知曉,此Bundle內部,因為access control的關係,與我們正常開發的專案內是完全斷開的,也就是在Test Bundle內的所有Test Class皆取用不到此專案內的所有東西。

要解決此問題,在Unit Test 的Test Class內,記得在程式碼的最上頭輸入:

@testable import 此專案名稱

透過上面方法,才能在Unit Test 的Test Class取到專案中所有的Object,進而可測試內部的Unit。

所有的Test class皆繼承自XCTestCase,並自帶提供setUP()和tearDown()函數供使用,這兩個函數會在調用每個Test Method前和後調用,內部分別用來設定想測試的狀態(ex :初始化某些class,給定某些property定值….以進行測試。)及將變數摧毀掉,避免記憶體流失問題(ex :將變數nil掉)。

不論是一個Test Method、一個Test class、一個Test Bundle、甚至是所有的Test Bundle、皆可點選灰色箭頭來進行測試,若成功則變為綠色箭頭,若失敗則標為紅色箭頭。

p.s. 也可使用command 加 U 來跑所有的Test Bundle

若建立的為UI Test ,則可點選下方的紅色小點進行螢幕錄製。

(4)測試後結果展現

Measure Time

在Test Class中,自帶的函數testPerformanceExample內呼叫了Test Class的measure函數,此函數的block內的程式碼,會跑10次,測量平均時間及標準差,進而設定baseline,供開發者判斷執行時間是否有異常發生。

Baseline存在不同的simulator內,開發者可以在不同的simulator內觀察各裝置硬體條件模擬下的執行時間。

Code Coverage

Code Coverage用來衡量程式碼內,程式碼被測量的比例和程度。

要搜集Code Coverage的資料,先要先將“Gather Code Coverage”打勾

按下command 加 U 跑完所有測試後,在下方的畫面,可以看到程式碼中每個.swift檔案內的覆蓋率,每個.swift檔案旁邊的灰色小箭頭點下後,可進入程式碼內。

在程式碼內,最右方的數字代表測試程式跑過該段程式碼的次數。紅色表示0次,看一下程式碼內,0次的部分為error時會進入的部分,滿合理的,代表程式中並無error的情況。

在測試過程中,下方Console區也會秀出目前測試的情況

到此為止,Unit / UI Test已大略簡介完畢,在進入C.I.前,先做個結論。

在測試的領域中,難的不是擔心測試不過,最難的是如何寫出可測試、好測試的程式碼。

在物件導向的領域中,我們期望開發出高內聚、低耦合的程式碼。類別跟類別間相依性低,可以很容易斷開,不需要一個類別修改,另一個類別也必須大變動。

因此程式碼的架構對於測試程式的撰寫就十分重要。打開一個專案,MVC架構完全沒切分,全部寫在view controller內,我想是很難撰寫測試程式的。

推薦下面這本書和官方文件,教導開發者如何寫出好測試的代碼:

p.s. 小訣竅:善用protocol- oriented programing來撰寫swift程式碼,對於測試程式的開發非常有幫助!

Keyword :TDD、BDD、Mock、Stub、Fake、Inject Dependency、SUT、Code Coverage

若非獨立開發者,在團隊中通常開發者為兩位以上合作開發,經過Git流程,還要給測試人員測試,待一定進度後還必須推上testFlight供團隊其他人測試,最終送上App Store,後續若有改版,還得再走一次這個流程。

要是這一切流程可自動化,不是很方便嗎?設定個時間區間,自動去遠端Git上抓檔案下來測試,產生測試報表,並發送email給相關團隊人員確認進度,接著自動推上testflight及AppStore。

這一切自動化的概念即持續整合(Continuous-Integration),將這些非技術類的麻煩事交由電腦完成,團隊人員各司其職,開發者專注於開發,不必因這些雜事浪費時間。

本文要介紹的是使用Jenkins來進行C.I.

當然,xcode也有屬於自己的C.I.程式,但在本文就不多作介紹,有興趣的可自行研究。

推薦看“About Continuous Integration in Xcode”的章節,對於iOS開發的C.I.背景知識可更加了解。

接著開始進入Jenkins的世界,首先下載最新版的Jenkins

下載並安裝後,會自動跳出瀏覽器到“http://localhost:8080/”頁面,此為Jenkins的控制台。

若無法跳到http://localhost:8080/的頁面,也許是Java版本過舊,建議是JDK 1.7以上的版本。將以下兩個都進行安裝。

安裝完成後,可以新增使用者,未來就用這個帳號密碼進行登入。要求安裝Plug-In頁面可先打叉叉,之後再安裝即可。

進入左側的“管理Jenkins” -> “管理外掛程式”,安裝”Email Extension Plugin”、“GitHub Plugin”、“Gitlab Plugin”、”Keychains and Provisioning Profiles Management”、“Xcode integration”、“JUnit Plugin”、“ JUnit Realtime Test Reporter Plugin”,有些外掛並不會在本文用到,但未來有需要。

首先,先建立email通知,當測試失敗時可以立刻通知團隊人員。進入左側的“管理Jenkins” -> “設定系統”,拉到最下面“電子郵件通知”,以gmail為例,可寄測試信給自己試看看。

上方的“擴充電子郵件通知”為剛安裝的外掛,可設定更多關於電子郵件的內容,在此就不做介紹。

接著在“管理Jenkins” -> ”Keychains and Provisioning Profiles Management”

keyChain的話請選擇系統中的login.keychain檔案位置,將其上傳。

Identitles欄位,請填上“iPhone Distribution: ………………”,後方為開發者名稱及代碼,可以在keyChain程式內尋找到。

Porvisioning Files部分,請到如下的路徑,選擇Porvisioning Files的資料夾,將其上傳。Filename及UUID請選擇資料夾內的.mobileprovision檔案,將其填入。

若上兩步驟皆失敗的話,也可參考如下另一個作法。直接指定keychain和Porvisioning Files

到目前為止已完成初步設定,接著來建立新專案及設定內部所需環境。

回到主頁,點選左側的“新增作業”,填完自定義的專案名稱後,選擇“建置free-style專案”,進入後,選擇左方的“組態”來進行設定。

進入“組態”後,專案名稱填入剛設定的專案名稱

往下拉到“原始碼管理”的部分,填入遠端git網址,如下圖。

“建置觸發程序”的部分,可設定固定時間,程式自動從git抓檔案下來測試,再產出報表,也可設定只要有開發者推新版本到git,即可自動執行。這些功能就留給大家測試了。

“建置”部分,如下圖。最常用到的是Xcode和執行Shell,Xcode即我們一開始安裝的xcode plugin。

以執行Shell為例,可以把它當作電腦上的command line輸入所需指令。輸入xcodebuild的指令,讓Jenkins自動執行設定的流程。

關於xcodebuild的指令,可參考下列連結。也可打”xcodebuild-help“來觀看

試試在shell欄位輸入,待建置後,讓Jenkins自動build專案:

xcodebuild -configuration Release -target TestingExample

若選擇“Xcode”的話,在“General build settings”的部分,Target留白的話,代表會執行專案所有的target,也可指定特定的test target,執行相關的測試檔案。

Configuration填入Debug

Advanced Xcode build options‘部分如下設定即可,SDK和Custom xcodebuild arguments部分也可再行設定,在此就不多作介紹。

上方提到的“執行shell”和“xcode”兩種方式,個人推薦使用“執行shell”。因不需多做設定,且自由度高。

“建置“設定完後,最後為“建置後動作”,可在建置失敗時寄信通知相關人員。

回到專案的主頁,點選左方的“馬上建置”即可開始自動執行專案。下方為建置後的結果列表。若發生錯誤的話,會立刻寄信通知。

建置後,可進去當次建置的歷程觀看結果。點選“Console Output”,了解進一步的詳細資訊,若有錯誤發生的話,也可觀看錯誤原因。

關於Jenkins的介紹大概到這邊。Jenkins的設定繁雜,很多功能需要多加研究,剩餘本文未提到的部分,就留給大家自己試看看了~

最終專案如下:

--

--