qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          iOS開發中的單元測試(二)——讓斷言活潑起來的匹配引擎

           上一篇文章簡單介紹了OCUnit和GHUnit兩款iOS開發中較為常見的單元測試框架,本文進一步介紹單元測試中的另一利器——匹配引擎(Matcher Engine)。匹配引擎可以替代斷言方法,配合單元測試引擎使用,測試用例可以更多樣化,更細致。

            傳統斷言提供的方法數量和功能都有限,以導讀中提到的兩款框架為例,即使是斷言相對豐富的GHUnit也只是提供了38種斷言方法,范圍僅涵蓋了邏輯比較,異常和出錯等少數幾方面,仍然很單一。而使用匹配引擎代替斷言,可能性就大大豐富了,除了普通斷言支持的規則,一般的引擎還默認提供了包含,區間,繼承關系等。更重要的是,使用匹配引擎開發者可以自行開發匹配規則,引入與業務相關的邏輯判斷。

            本文要介紹兩款匹配引擎,一款就是Hamcrest的Objective-C實現——OCHamcrest,另一款則是專為Objective-C/Cocoa而生的后來者——Expecta。接下來將結合GHUnitTest,介紹兩款匹配引擎如何在單元測試中發揮作用(有關GHUnitTest參考《iOS開發中的單元測試(一)》。

            OCHamcrest

            介紹匹配引擎必須要提Hamcrest,幾乎已經成為匹配引擎的代名詞。官網首頁上的一句話表明了它的身世:“Born in Java, Hamcrest now has implementations in a number of languages.”。這款誕生于Java的匹配引擎現在還支持除Java的PythonRuby、PHP、Erlang和Objective-C。

            加入工程

            在iOS工程中使用OCHamcrest需要先獲取OCHamcrestIOS.framework,可以從Quality Coding直接下載,或在Github上獲取源碼編譯。注意:Github上托管的OCHamcrest工程以Submodule的形式關聯源代碼,因此如果使用命令行方式clone工程,需要執行“git submodule update --init”。

            下載源碼后,進入Source目錄,執行MakeDistribution.sh腳本,將會在Source/build/Release下生成OCHamcrest.framework、OCHamcrestIOS.framework和OCHamcrest.framework.dSYM , OCHamcrestIOS.framework就是iOS工程中需要用到的框架,如圖1。

            圖1,從源碼編譯生成 OCHamcrestIOS.framework

            打開已經安裝了GHUnitTest的工程,把OCHamcrestIOS.framework添加到單元測試的Target中。在需要使用匹配引擎的用例中,定義“HC_SHORTHAND”并導入“<OCHamcrestIOS/OCHamcrestIOS.h>”(如圖2)。

            圖2,把OCHamcrestIOS.framework導入工程

            至此OCHamcrest已經安裝完成,可以再測試用例中使用匹配規則代替GHUnitTest的斷言方法。

            預定義規則

            OCHamcrest針對不同的數據類型提供了大量的預定義匹配規則,大大豐富了斷言的類型。支持的數據類型包括:對象、容器、數值和文本,此外還提供了專門的邏輯匹配規則。

            以文本(一般就是NSString)為例,OCHamcrest提供了6種針對對象的匹配規則:

          IsEqualIgnoringCase,該文本是否與給出的文本相同(忽略大小寫);
          IsEqualIgnoringWhiteSpace,該文本是否與給出的文本相同(忽略空格);
          StringContains,該文本是否包含給出的文本片段;
          StringContainsInOrder,該文本是否按照先后順序包含給出的若干文本片段;
          StringEndsWith,該文本是否以給出的文本片段結尾;
          StringStartsWith,該文本是否以給出的文本片段開頭。

            另外,再舉OCHamcrest為對象(NSObject和NSObject的子類)預定義的8條規則:

          ConformsToProtocol,該對象是否遵循了給出的協議,或者說是否實現了給出的Delegate;
          HasDescription,允許使用文本規則對給出的一段文本與該對象的描述進行匹配;
          HasProperty,該對象是否含有給出的屬性;
          IsInstanceOf,是給出的類的實例,或是給出的類子類的實例;
          IsTypeOf,是給出的類的實例,不同于IsInstanceOf,無法匹配子類實例;
          IsNil,為空;
          IsSame,與給出的對象是同一個實例。

            撰寫用例

            OCHamcrest提供了匹配規則和相應的斷言方法,配合單元測試框架(本文以GHUnit為例, 在《iOS開發中的單元測試(一)》中已經介紹了如何安裝GHUnit框架并撰寫用例)的驅動機制即可撰寫用例。本文以聯合使用上述提到的StringStartsWith和HasDescription規則為例。

            首先,定義一個用于示例的類“Man”(如圖3),有屬性friends,當friends為空,其description為“Man without any friend, so sorry.”,反之為“Nice persion with [friends count] friend(s).”。(使用Foo或Bar這樣的示例會顯得很沒情懷吧 ;-|)

            圖3,用于測試的類:Man

            用例中判斷某Man實例的description是否以Nice開頭(這是不是一個友善的人),如圖4。

            圖4,測試用例兩則

            UntTestCase是GHTestCase的子類,引入<OCHamcrestIOS/OCHamcrestIOS.h>并定義HC_SHORTHAND表示使用OChamcrest。setUp方法在每個測試方法執行之前初始化一個Man實例;testANiceMan方法向Man實例的friends屬性中加入兩個值,因此該實例的description將返回“Nice man ....”;使用OCHamcrest提供的斷言方法assertThat與匹配規則配合,判斷該實例的description是否以“Nice”開頭;testNotANiceMan方法則直接測試一個未經過加入friends的實例測試。

            上述測試,testANiceMan方法順利通過,testNotANiceMan不會通過,直接報出錯誤堆棧,并打印在匹配規則中預先定義好的出錯信息(如圖5)。

            圖5,測試結果

            輔助方法

            Syntactic Sugar是一種提高匹配規則和斷言可讀性的方案,讓一個匹配和斷言看起來更像是一句自然語言的話,而非多個函數的堆砌,對實際的匹配運算不產生任何影響。例如,沒有加Sugar的匹配:

          assertThat(foo, equalTo(bar));

            加Sugar可以是:

            assertThat(foo, is(equalTo(bar)));

            除了Sugar,OCHamcrest還提供了describedAs方法,用于輔助斷言方法,自定義出錯文案,例如:

          assertThat(foo, describedAs(@”foo should be equal to bar”, equalTo(bar), nil));

            自定義規則

            OCHamcrest官方給出的自定義匹配規則示例是:onASaturday,判斷一個NSDateComponents實例是否為星期六。本文以上一節使用的Man對象為例,匹配某Man實例是否有一個名為“Joe”的好友,規則命名為:hasAFriendJoe。

            自定義匹配規則包括兩部分,一個Macher類和一個用OBJC_EXPORT方式定義的函數。

            自定義Macher類都是HCBaseMatcher的子類(如圖6),接口中定義的類初始化方法供匹配方法hasAFriendJoe調用,其實現則通過調用接口中定義的另一個實例方法。

            圖6,HasAFriend接口和匹配方法hasAFriendJoe定義

            在HasAFriend中需要引入<OCHamcrestIOS/HCDescription.h>,并重寫父類中的matches:和describeTo:方法(如圖7)。在maches:方法中實現匹配邏輯,匹配成功則返回YES,否則返回NO;describeTo是失敗后的描述;hasAFriendJoe方法只需要調用類方法初始化匹配規則類即可。匹配規則定義后,可以配合斷言方法使用,如上一節所示的assertThat方法:

            assertThat(self.man, hasAFriendJoe());

            圖7,規則實現

            Expecta

            Expecta專為Objective-C/Cocoa而生,相比OCHamcrest,其優化了匹配的語法,測試用例的可讀性更高。此外,Expecta對匹配對象類型沒有強制要求,允許任意類型的數據進行匹配。在OCHamcrest中每一條匹配規則都是一個方法,規則聯合使用也需要以參數形式傳遞。在Expecata中聯合規則的語法是以點號連接,借助Sugar介詞可以把一個聯合規則拼裝成一句符合自然語法的句子,例如:

          OCHamcrest —— assertThat(@"foo", is(equalTo(@"foo")));
          Expecta —— expect(@"foo").to.equal(@"foo");

            · 加入工程

          Expecta提供了CocoaPods的源,可以通過定義依賴引入:
          dependency 'Expecta', '~> 0.2.1'
          dependency 'Specta', '~> 0.1.7' # specta bdd framework

            或者從github上獲取源代碼,編譯出Library,引入XCode工程。下載源碼后,進入工程目錄,運行rake,編譯工程。編譯完成后,把products目錄拷貝到工程中(如圖8),在iOS/MacOSX工程中加入響應的.a文件(如圖9)。在Build Settings的Other Linker Flags中加入-ObjC參數(在《iOS開發中的單元測試(一)》中添加GHUnit一節介紹了如何添加-ObjC的參數)。與OCHamcrest類似,在測試用例中定義EXP_SHORTHAND,并引入“Expecta.h”(如圖10)。

            圖8,加入編譯后的頭文件列表

            圖9,加入Library文件

            圖10,用例中引入Expecta.



          · 預定義規則

            Expecta提供的預定義規則只有20條,遠遠少于OCHamcrest提供的預定義規則。由于Expecta的匹配規則對匹配對象沒有要求,因此沒有提供像OCHamcrest中針對某種對象的特定規則。

            在Expecta的github首頁可以看到全部預定義規則列表。舉幾個較有特點的規則為例:

          expect(x).to.beCloseToWithin(y, z),x距離y的距離小于z
          expect(x).to.beTruthy(), x是否為真(或非空);
          expect(x).to.beFalsy(),x是否為假(或空/零);
          expect(^{ /* code */ }).to.raiseAny(),該Block是否拋出異常;
          expect(^{ /* code */ }).to.raise(@"ExceptionName"),該Block是否拋出給定名字異常。

            此外,通過.notTo或.toNot對規則取反進行匹配,如:expect(x).notTo.equal(y)。

            通過.will或.willNot進行異步匹配,即在超時時間(默認超時1秒,也可通過[Expecta setAsynchronousTestTimeout:x]設定)之前滿足匹配規則即可,如:expect(x).will.beNil()。

            · 撰寫用例

            Expecta不使用類似assertThat類似的輔助斷言方法,而是直接使用expecta.語法匹配。

            仍然以GHUnit測試用例為例,測試一個數字n,是否在5附近,距離小于2,即處在[3,7]區間內(如圖11)。

            圖11,Expecta測試用例

            Expecta不支持匹配規則的聯合使用。

            · 輔助方法

            Expecta也有語法Sugar:to。

            · 自定義規則

            Expecta的自定義規則有兩種方式,靜態規則和動態規則。

            定義靜態規則:

            Expecta的匹配規則不是一個類,是通過框架提供的宏定義來實現的,操作比定義OCHamcrest規則簡單不少。仍以OCHamcrest中的判斷一個Man實例是否有名為“Joe”的好友。

            通過EXPMatcherInterface()方法定義,該方法有兩個參數,規則名和規則參數列表。示例如圖12。

            圖12,擴展規則hasAFriendJoe定義

            EXPMacherInterface第二個參數允許通過這樣的方式定義列表:(NSString *Foo, int bar)。

            在實現中,用EXPMatcherImplementationBegin和EXPMatcherImplementationEnd標示規則實現的頭尾。并定義prerequisite、match、failureMessageForTo和failureMessageForNotTo四個Block,分別返回與判斷結果,匹配結果,正向匹配出錯原因和反相匹配出錯原因(如圖13)。由于Expecta框架不支持ARC,因此需要在Build Settings中對該.m文件添加 -fno-objc-arc參數。在測試用例中可以通過如下語法調用:

          expect(self.man).hasAFriend(@"Joe");

            或反相匹配:

          expect(self.man).notTo.hasAFriend(@"Joe");

            圖13,Expecta自定義規則實現

            定義動態規則:

            動態規則是本質上并不是一段邏輯匹配,而是通過Expecta的語法對匹配對象的屬性進行是否為真的斷言。例如:

          @interfaceLightSwitch:NSObject
          @property(nonatomic,assign,getter=isTurnedOn)BOOLturnedOn;
          @end
          @implementationLightSwitch
          @synthesizeturnedOn;
          @end

            可以寫出如下斷言:

            建立動態規則:

          EXPMatcherInterface(isTurnedOn, (void));

            就可以通過以下斷言判斷turendOn屬性的真假:

          expect(lightSwitch).isTurnedOn();

            總結

            整體看兩款匹配引擎,Expecta小巧,敏捷,提供了多種靈活的匹配方式,OCHamcrest從Hamecrest體系繼承而來,形式更加中庸,提供的機制更完善。從開發者的角度看,Expecta更好玩,而OCHamcrest更實用,在實驗性的項目中我會偏向選擇Expecta,而較正式的項目則會使用OCHamcrest。

            OCHamcrest結合上一篇《iOS開發中的單元測試(一)》中介紹的單元測試框架GHUnit可以給開發者提供一個完整的單元測試方案,建議開發者在自己的項目中引入這樣的質量自控機制,寫出健壯的代碼。

            通過兩篇文章介紹了單元測試框架和匹配引擎的一些基礎知識,在接下來的文章中,我將結合一個項目,從實戰角度詳細記述如何開發帶有單元測試的iOS項目。

          相關文章:

          iOS開發中的單元測試(一)

          posted on 2013-07-04 10:42 順其自然EVO 閱讀(355) 評論(0)  編輯  收藏


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          <2013年7月>
          30123456
          78910111213
          14151617181920
          21222324252627
          28293031123
          45678910

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 桐城市| 盖州市| 饶阳县| 东兴市| 滨州市| 缙云县| 阳泉市| 神农架林区| 钟山县| 都匀市| 会同县| 澄迈县| 马公市| 墨江| 交口县| 武清区| 东台市| 依兰县| 多伦县| 东辽县| 金塔县| 泸水县| 大同县| 富宁县| 三明市| 桃园县| 平舆县| 化州市| 承德县| 新绛县| 于田县| 新密市| 沙湾县| 越西县| 邯郸县| 龙岩市| 焦作市| 岗巴县| 积石山| 临洮县| SHOW|