開始以前程序員
本文側重講述如何在iOS程序的開發過程當中使用單元測試。使用Xcode自帶的OCUnit做爲測試框架。編程
1、單元測試概述安全
單元測試做爲敏捷開發實踐的組成之一,其目的是提升軟件開發的效率,維持代碼的健康性。其目標是證實軟件可以正常運行,而不是發現bug(發現bug這一目的與開發成本是正相關的,雖然發現bug是保證軟件質量的一種手段,可是很顯然這與下降軟件開發成本這一目的背道而馳)。它是對軟件質量的一種保證,例如重構以後咱們須要保證軟件產品的正常運行。網絡
不少人認爲編寫單元測試沒有用是認爲單元測試並不能保證必定能減小bug發生的概率,而因爲編寫單元測試必定會花費必定的時間與精力,於是必然的會增長成本。客觀的說,形成這種緣由很大的程度上是程序員的水平不夠高。我認爲使用使用單元測試帶來巨大好處的必要條件以下所示:架構
程序員自己的編程水平--是否有較多的代碼經驗,是否熟練掌握重構框架
程序員對項目的認知--是否能正確理解軟件或模塊的需求異步
項目質量--是否穩定,是否長期多版本,是否須要應對較多變化ide
若是程序員水平較高,對需求理解較爲清晰,項目須要面對較多的變化,那麼毫無疑問單元測試對於軟件很是有益。假如軟件功能簡單且開發週期短,不須要進行復雜的維護工做,那麼單元測試的意義並不大。函數
優秀的單元測試實踐的好處:單元測試
好的單元測試就是一份好的文檔,而且比文檔更能爲程序員所接受,它直接描述了測試員對受測代碼的結果所持的預期。
當代碼由別人維護時(或本身進行重構時),經過單元測試的約束,才能保證在加入新功能或修改舊功能時代碼的正確性。
因爲單元測試的自動化執行,保證了在整個開發流程中代碼都會被測試,這很是符合XP思想。
保證在面對軟件功能的變化時,程序員能夠較爲放心的進行代碼重構,而沒必要擔憂是否破壞了原有功能。
好的單元測試能夠下降bug數量,而對於項目管理來講,修改bug這個過程是沒法制定計劃的,可使軟件的開發流程更容易掌控。
能夠由老程序員編寫描述某個類行爲的測試,以此指導新程序員對類的編碼。
……
好處還有不少,但最重要的一點就是保證了軟件質量的同時,因爲減小bug和應對變化形成的迴歸bug的產生等,提升了勞動生產率。並且,在敏捷流程中,使用單元測試是必須掌握的手段,不然就沒發保證重構的正確性,從而形成代碼沒法面對變化。
2、iOS的單元測試概述
剛接觸客戶端編程時,我在很長一段時間內都想不通對於客戶端程序如何編寫單元測試。單元測試本質上說白了就是用一些斷言來斷定結果,而這種方式是如何應用到具備複雜交互的界面測試上來的呢?
咱們要作的就是將客戶端代碼轉化爲易於測試的代碼。什麼樣的代碼易於測試呢?它至少是這樣的:
一、被測方法須要產生可測量的結果。
二、類之間的關係應該是鬆耦合的。
其中第一條是必要條件。使用斷言這種形式指明瞭測試的方法最終要形成某些能夠度量的結果。於是,咱們須要儘可能的將展現和業務邏輯分離開來。展現的代碼是無法測試的,例若有的方法只是播放動畫。而業務邏輯最終都會形成一些數據的改變,這是容易測試的。
大略的講,做爲一個iOS程序員來講,首先要了解一個叫作MVC的模式。這個模式定義了Cocoa Touch框架的整體結構。在iOS程序中,咱們也須要按照這種模式進行界面代碼的編寫。這樣設計出來的類具備較好的結構,且比較適合於作單元測試。
而後必定要懂得不停重構代碼,這樣咱們才能使代碼不停地改善,不停地變得更加適合單元測試。
有一些框架能夠幫助你們更好的測試,分別是OCUnit、GTM、GHUnit、CATCH、OCMock,但目前對我來講,OCUnit足夠用了。做爲蘋果官方提供的測試框架,它最大的優勢就是簡單易用。
3、單元測試實踐
下面是一些我所理解的單元測試中比較好的實踐。
顧名思義,單元測試面向的對象是單元,這個專有名詞源自編譯器領域的術語「編譯單元」。在面向過程當中,指的是函數,而在面向對象中,指的一般就是「類」。於是,每一個功能類都應該提供對應的單元測試。
實踐1 每一個功能類都應提供單元測試,且每個測試類,只依賴於其要測試的受測類。使用僞造對象能夠避免對其餘類的依賴。
解釋 保證一個測試類只關注一個被測類,當測試不經過時,就能迅速的定位到是誰發生了錯誤,而不會受到其餘類的干擾。
簡單的數據類等能夠不提供,可是要保證該測試的都要覆蓋到。並不存在一種合適的度量指標能夠量化地判斷某種單元測試方案是否成功。經常使用的標準(代碼覆蓋率和成功執行的測試用例數)均可以在受測軟件的質量不變的狀況下人爲的修改(做假)。固然,在沒法確保程序員素質的狀況下,做爲沒有辦法的辦法,使用這種標準也是能夠的(或者無奈的說,必須的)。單元測試須要程序員本身把關,關注哪些功能確實須要測試覆蓋。這也就是前面所說的一些程序員不相信單元測試能夠提升生產率的理由--它更多的依賴於程序員的素質,這是沒有保證的。但一樣的,因爲敏捷是一種以人爲本的思想實踐,於是這種行爲彷佛又是一種必然。
實踐1.1 使用僞造類避免對其餘類的依賴。
解釋 避免依賴的一種手段。
例如,某個被測的方法聲明是這樣的:
-(void)xxxx:(Person *)person;
若是測試時傳入Person的話,就形成了測試類依賴於兩個類。當因爲person中的錯誤引起測試不經過時,就不能迅速的定位到受測類中是否有問題。遇到這種狀況,就可使用僞造類。假如方法中只使用了person的一個屬性name,那麼能夠將方法名重構爲
-(void)xxxx:(id)person;(此處id有待商榷,只是這樣作最簡單)
而後在單元測試的target中添加只包含name屬性的fakePerson來做爲僞造類。這樣,一旦發生錯誤就能夠迅速的推測出錯誤的來源。
實踐1.2 使用僞造環境避免其餘環境的干擾。
解釋 適合於異步的方法測試。
很常常遇到的一種狀況是測試有網絡環境的代碼。因爲異步的存在,這會形成測試代碼很差寫。一種簡單的解決方法是,咱們假定網絡必定是通暢的,則咱們測試的代碼將分爲兩部分,即拼裝發送功能和接收解析功能。假如發送和接收功能各自都能經過測試,那麼咱們大約能夠肯定這個異步方法的正確性。另外一種方法是使用GHUnit,它支持異步代碼的測試。
實踐2 測試用例(方法)名應該是自解釋的且是獨立的。
解釋 基本功。
若是被測試類的名稱是XXX,那麼測試類能夠命名爲XXXTests。而對於其中要測試的功能,命名應該是自解釋的。這能夠在發現錯誤時儘快的定位問題所在。例如,若是某個屬性obj應該是非空的,那麼咱們能夠將其命名爲:
-(void)testObjNotNil{}
每一個方法目標應該是單一的,大多數狀況下每一個方法內都只有一個斷言語句;方法不該該依賴於其餘方法的結果做爲輸入,保證原子性。
實踐3 斷言語句須要解釋測試者的意圖。
解釋 基本功
每種單元測試框架都提供了不少斷言語句,從根本上來講它們都是同樣的。可是測試者須要根據本身的目的選擇適當的語句,這樣纔可讓別人閱讀測試代碼時理解用例設計的目的。例如對於STAssertNil和STAssertNotNil等等。
實踐4 判斷某個意圖有沒有達到的很好的方法是檢測方法影響的數據有沒有合理的變化。
解釋 基本功
因爲單元測試是使用斷言語句來作判斷的,於是最容易作的就是判斷數據的變化。這也就限定了單元測試能測試的方法範圍,即引發數據變化的方法。對於一些純展現的方法,例如播放一段特效,這種方法是沒法靠單元測試來進行約束的。測試數據的特性包括取值範圍(int、float等),排列順序(NSArray等),類型等等。
實踐5 運用重構的手段使方法變得易於被測試。
解釋 單元測試是保障重構安全的手段,重構也可使代碼易於被測試。
什麼樣的代碼是容易進行單元測試的?最簡單的一點就是,每一個被測方法都應該是功能單一的。固然,這也是代碼規範中應該作到的。方法的功能單一,則測試方法的斷言也會比較好肯定。若是你發現某個方法很難進行測試,則就應該對這個方法進行拆分重構。
實踐5.1 面向抽象設計類之間的關係。
解釋 利於僞造類的實現。
類之間通信若是依賴於抽象(接口),則能夠較容易的使用僞造類。參照實踐1.1。
實踐6 運用自上而下的方式構建類。
解釋 自上而下的方式可使類的功能明確,類的構成將會清晰緊湊,不會出現一些廢方法。
先肯定類須要負擔的責任,以此來肯定類具備的公有方法以及屬性。經過重構將公有方法中的代碼轉化爲私有方法,以使方法儘可能短小緊湊。
實踐6.1 應對全部暴露的屬性和方法提供測試,私有方法則沒必要。
解釋 若是運用自上而下的方式構建類,則理論上私有方法應該都是公有方法重構而獲得的。實際上測試公有方法時這些私有方法都應該被測試到了。並且,因爲私有方法相對公有方法來講發生變更的可能性很大,會形成沒必要要的修改測試代碼的成本。
回調方法不屬於私有方法,也須要進行測試。
實踐6.2 回調方法的測試方法是直接調用。
解釋 基本功
因爲回調方法通常是異步和不可觸發的(按正常流程),例如網絡事件的返回和按下按鈕的觸發事件。於是,測試的時候要直接調用來對其流程進行檢測。例如某個按鈕的touch up inside事件:
-(void)buttonPressed:(id)sender;
能夠根據方法中用到的方法、屬性僞造一個FakeButton按鈕做爲參數傳遞進行測試。
實踐6.2 測試私有的方式,KVC、子類化和類別。
解釋 基本功。
遇到須要經過驗證私有數據才能編寫的測試時,能夠考慮使用KVC和子類化。子類繼承於被測類,只包含於單元測試target,其做用就是在不應變受測類的狀況下,使受測類具備某些易於被測的能力。
實踐7:變化須要新測試的支持。
解釋:保證測試的覆蓋度。
就像敏捷中提到的「改變須要抽象」同樣,在測試中改變須要新的測試。固然,度依然由程序員本身掌控。
4、通常流程
使用OCUnit最大的好處就是流程很是的簡單,簡單到讓你以爲很是愉悅。因爲有XCode的支持,添加測試變得異常簡單。只要在新建工程時勾選「Include Unit Tests」,就會自動的加入一個示例。而後再須要添加新的單元測試時,新建一個「Objective-C test case class」就能夠了。
測試文件中,只要知道setUp是初始化的地方,tearDown是結束清理的地方,並且它們在每一個用例方法執行時都會從新執行--這保證了測試用例的原子性。而後知道每一個測試用例都是以test做爲前綴的,而且無返回值。而後在方法中編寫斷言語句就能夠了。輸入STAssertxxxxx就能夠看到它們的聯想提示。編寫完成後,執行菜單Product->Test,單元測試就完成了!
5、測試驅動(TDD)
敏捷當中提到了TDD這種開發方式。TDD的主旨是使開發者對其編寫的代碼更有信心,使開發者修改代碼時內心更加踏實。對於其總結,仍是引用原文比較穩當:「測試驅動開發的妙處即在於,它以需求爲引領,經過測試的形式,來指導開發者進行軟件的設計與架構,並編寫出最爲精煉的代碼,使得測試用例運行經過。通過適當的重構以後,測試用例與產品代碼可達到較爲健康的狀態。」也就是上面提到的,經過自上而下的形式設計類,經過單元測試來不停地審視和重構類,從而達到代碼的健康。
若是在代碼寫完以後在編寫單元測試,那麼就體現不出這種模式的好處了。這就好像寫完代碼再補文檔同樣,沒有什麼意義。測試應該在代碼開始以前,或者在代碼編寫中不停地進行編寫更新,這樣才能使代碼不停進步。這也正是TDD的意思。
6、總結
單元測試的代碼如此簡單,可是想寫好單元測試卻並非一件簡單的事情。它須要程序員比較深的功底。因爲我的水平所限,有一些東西說的比較囉嗦。把複雜問題簡單化是本事,任重而道遠。但願你們能夠在平常開發中運用好這種簡潔高效的技術。