天天看點

iOS中如何對具有複雜依賴的SDK在真機上進行單元測試

單元測試在軟體開發中一直有着極其重要的地位,ios的開發也不例外。随着app規模的不斷膨脹,開發也逐漸的趨向子產品化,開發者常常以庫的形式封裝功能,最後組成app。此時由于app結構變得複雜,各種庫又可能存在着互相依賴的緣故,單元測試也随之變得複雜起來。開發者可能面臨着一系列問題,比如:單元測試如何處理這些依賴?如何在真機上運作測試?如何在app所在的環境中運作測試?本文将用一個模拟的開發環境逐一進行讨論。

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#section-1">問題</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#sdk">搭建sdk開發環境</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#ec3rdframework">第三方庫:ec3rdframework</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#sdkecsdk">開發中的sdk:ecsdk</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#ecapp">最終應用:ecapp</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#section-2">添加單元測試</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#section-3">編寫測試用例</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#app">讓測試運作在app環境</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#section-4">讓測試運作在真機上</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#section-5">特殊情況</a>

<a href="http://feihu.me/blog/2016/unittest-for-framework-in-big-project/#section-6">結尾</a>

在剛剛接觸軟體開發時,從未想過要寫單元測試,總覺得自己寫的代碼品質很高,根本不需要測試。需要将寶貴的時間放到開發上,測試是測試人員的事情。後面才發現,經常因為一個小需求的增加,動了一處代碼,結果其它地方出現重大問題,沒測試到就上線了。甚至到了後面,代碼複雜度越來越高,每動一處代碼都提心吊膽,生怕有其它情形未考慮到,如履薄冰。經曆了很多次慘痛教訓之後才醒悟過來,單元測試是保證代碼品質的不二法則。在《代碼重構》一書中,每進行一步重構,作者都會先運作一遍單元測試,然後再進行後面的重構,因為隻有這樣,才能夠保證重構之後代碼的正确性,如果連正确都無法保證,重構有何意義?

但是由于afnetworking本身的特點,決定了其單元測試環境其實是比較簡單的,比如:

afnetworking算是一個獨立的庫,并沒有依賴其它的第三方庫

不依賴複雜的app環境

不依賴真機環境

然而,很多時候,我們的開發環境比afnetworking複雜得多,比如:

依賴其它的第三方庫,如何處理這些依賴的問題?

依賴的某些第三方庫又必須運作在複雜的app環境中,如何讓測試運作于app環境?

某些方法必須在真機上才能運作,如何讓測試運作于真機上?

這些問題afnetworking的測試用例都沒有,而且預設建立的測試target都無法運作在這些環境中。如何利用xctest來對以上複雜情形下的sdk進行單元測試?我們從模拟以上開發環境開始。

首先我們來搭建一個滿足以上複雜條件但卻典型的開發環境:建立三個工程,其中ecapp是最終應用,它依賴了我們正在開發的ecsdk,而後者又依賴了第三方庫ec3rdframework。整個目錄結構為:

ec3rdframework是我們開發的ecsdk所依賴的第三方庫,其中包含一個<code>ecfoo</code>類,含有三個方法,分别模拟三種場景:

三個方法非常簡單的模拟了三種典型的場景,滿足條件時才會傳回yes,代碼很簡單。對于依賴應用環境的場景,是通過app設定的一個标志位來判斷,後面ecapp部分會看到這個标志位的設定。

其podspec如下:

ecsdk為我們所開發的sdk,它同ec3rdframework一樣,也是一個靜态庫,包含<code>ecusingfoo</code>類,與前面的<code>ecfoo</code>類包含相同的方法,每個方法直接調用<code>ecfoo</code>中對應的方法,這樣做是為了模拟依賴第三方庫的場景:

同前面類似,它的podspec如下,差別在于它多了對第三方庫的依賴:

也是因為這個依賴,還需要一個podfile:

ecapp為使用ecsdk的app,它啟動之後立刻調用ecsdk中暴露的接口:

方法的前兩行,先設定了app環境的辨別,前面<code>ecfoo</code>中便是依賴于此辨別來判斷是否處于app的環境中。

它的podfile也很簡單:

這裡有一點需要注意的是,實際上ecapp不會直接去依賴ec3rdframework,它是被ecsdk依賴,按理說不需要加到podfile中,cocoapods會幫我們處理這種依賴。但由于ec3rdframework并非已經釋出的第三方庫,如果不加上這一句的話,在<code>pod install</code>時會出現下面的錯誤:

[!] unable to find a specification for <code>ec3rdframework</code> depended upon by <code>ecsdk</code>

在ecapp路徑下執行<code>pod install</code>之後,然後編譯運作,将會得到以下日志:

表示庫已經正常調用,運作于app環境中的模拟器上。我們需要進行單元測試的開發環境搭建完成。

環境搭好之後,接下來,為ecsdk添加單元測試。由于xcode內建了xctest,是以添加單元測試非常簡單,依次選擇菜單項:<code>new/target/ios/test/ios unit testing bundle</code>,這裡我們的測試target為<code>ecsdktests</code>。完成後,在ecsdk工程中會生成對應的target和源檔案,可以看到工程中有一個ecsdktests.m檔案,這是xcode預設生成的測試用例,是來打醬油的,什麼事都沒做。選中ecsdktests這個scheme,按下⌘ u(注意,這裡是u,而不是平時所用的b和r)編譯并運作測試,因為此時是預設的空測試用例,是以測試很順利的完成:

iOS中如何對具有複雜依賴的SDK在真機上進行單元測試

為了測試ecsdk中提供的方法,我們需要為其添加新的測試用例。三種場景,隻有傳回yes時才算通過測試,由此表示測試可以運作于這些環境中:

測試用例很簡單,我們來看看是否可以運作。再次選中ecsdktests這個scheme,⌘ u編譯運作,此時出現以下錯誤:

錯誤資訊顯示連結時找不到<code>ecusingfoo</code>方法,後者是定義在ecsdk工程中,表示測試target需要依賴ecsdk。處理依賴有多種方法:可以在build settings中添加庫及其對應路徑。還有一種更好的辦法,利用cocoapods,在它的podfile中增加一個target即可,這樣可以保證ecsdktests和ecsdk的依賴完全一緻。新的podfile像這樣:

因為依賴了共同的庫,是以将這抽出來成為一個單獨的方法,接着在兩個target中調用。由于測試target是依賴于ecsdk,是以還需要加上:<code>pod 'ecsdk', :path =&gt; '.'</code>。重新<code>pod install</code>,⌘ u,編譯問題解決,測試可以正常運作。

iOS中如何對具有複雜依賴的SDK在真機上進行單元測試

但現在面臨兩個新的問題,因為現在測試隻能運作于模拟器上,而且并非是app的環境,是以後面兩個測試無法通過。

如果我們直接将scheme選成真機上運作,一按⌘ u便會彈出以下錯誤提示:

暫時無法運作于真機上。

retired document important: this version of unit testing guide has been retired. the replacement document focuses on the new testing features and workflow provided by xcode 5 and later revisions. for information covering the same subject area as this page, please see testing with xcode.

新的文檔中也沒有再提這兩個概念。但由于xctest的前身就是octest,是否配置的方法也是相通的?是否将測試target變成application tests之後,就可以運作在app環境中?抱着試一試的想法,按照廢棄文檔中的方法來配置測試target。

在general配置頁面,裡面有一個host application,這個便表示測試是否可以運作于app中。但由于目前測試的是一個靜态庫,無法選擇想要運作的app,此時需要用通過其它途徑來指定。在ecsdktests的build settings中修改兩處:

bundle loader: your/app/path/ecapp.app/ecapp

test host: $(bundle_loader)

再次運作,發現ecapp的應用先啟動,随後測試用例開始執行。因為ecapp在啟動之後便配置了app環境的标志位,是以環境依賴的測試用例可以正常通過,測試已經可以運作于app的環境中,我們的嘗試成功了。現在隻剩下最後一個場景,如何讓測試運作于真機上:

iOS中如何對具有複雜依賴的SDK在真機上進行單元測試

其實,在完成上一步的配置之後,測試已經從所謂的logic tests就轉變成了application tests,而後者對運作的環境是沒有限制的。直接将scheme設定成真機,先編譯一下ecapp,再運作一次測試,所有的測試可以通過:

iOS中如何對具有複雜依賴的SDK在真機上進行單元測試

注意:事情并不會總是這麼順利,有時候由于一個app過于龐大,各個庫的podspec寫得不是很規範,不是所有依賴的libraries都寫在了podspec中,有些被放在build phases裡面,系統庫尤為常見。這樣就導緻即使我們按照前面介紹的都配置好了,還是無法讓測試target編譯通過,在連結時會出現各種各樣的找不到符号的錯誤。此時需要手動去添加這些庫到測試target的build phases中。至于需要添加哪些,隻有根據編譯時的錯誤逐一添加了。而且有一點需要注意:有時庫的status需要是optional,否則最後連結的時候也會出錯。下面是一個真實測試用例在build phases中所依賴的庫:

iOS中如何對具有複雜依賴的SDK在真機上進行單元測試

它一共依賴了41個系統庫,每一個都是在編譯出錯時,查到缺少的符号所在的庫來添加的,是個體力活:-)。

至此,我們的測試用例已經可以運作于上面描述的幾種典型的複雜環境,其實最重要的步驟隻有兩步,第一步是設定依賴,處理各種編譯錯誤;第二步是設定build settings,将測試轉成application tests,讓測試能夠運作于app環境。

我們搭建的環境和真實的環境相比起來,複雜度還存在一定的差距,在編譯測試target時會出現各種各樣奇怪的問題,本文無法一一例舉,靠大家根據實際情況處理了。

新的一年,以這篇簡單的文章作為起始,祝大家新年快樂!

(全文完)