幾周前,我決定將將我在 mokacoding 上的創作更多集中在單元測試與驗收測試,自動化和生產效率上,主要在iOS領域。
相關深入文章可以看看“通過 CocoaPods 為 iOS 項目創建 Calabash 并構建配置”和“用終端運行 Xcode 測試”。
這周我們要回過頭來看看,或者說是站在更高的角度審視單元測試和驗收測試,以及在云端運行持續集成有哪些資源。
就像有人創建 walking skeleton 時會做的事情一樣,我們也將先查看 Cocoa 和 Xcode 提供給開發者的工具,然后再看看能實現更好效果的開源庫,最后整理出在云端持續集成環境運行測試的解決方案。
伴隨著 iOS 7 和 Xcode 5,蘋果發布了 XCTest,一個簡單而又強大的測試編寫框架,使用了同 xUnit 一樣的風格。
編寫 XCTest 測試很簡單,開發者在 Xcode 點擊 ?U 運行測試便能持續不斷地迅速獲得反饋。
Xcode 還有一個叫“Test Navigator”的界面,它可以讓我們看到所有測試點,包括在最后一次運行后的成功或失敗狀態。
值得注意的是,紅色為測試失敗,綠色為測試通過。在不斷迭代過程中顏色會給你很大幫助。
XCTest 已經高度集成在 Xcode 中,使用簡單方便。這是它主要優點,也是缺點。XCTAssert
類 API 并不容易理解,也不靈活。從 Xcode 外邊運行測試也沒有你想象的那么簡單。
在過去兩年中,iOS 和 OS X 的單元測試框架已經變得越來越好,而驗收測試這邊反而沒什么進步。
蘋果提供了 UIAutomation 框架來編寫 UI 自動化測試。UIAutomation 測試使用 javascript 寫成。允許用戶使用代碼驅動應用 UI 并給它的狀態設置斷言。盡管看上去很美好,使用 UIAutomation 其實是很繁瑣的, Javascript API 也沒有原生代碼寫成的單元測試那樣強大。
這是 UIAutomation 測試的一個小片段。
你可以看到的,javascript API 比 Foundation 中的那些更加冗長。再加上這種測試需要在 Instruments 中運行,你就可以想象使用這個框架是多么的不爽。
最后是蘋果的 CI 解決方案:Xcode Bots。我們可以配置一個 Xcode Bot,在需要的時候觸發他工作,例如運行我們的測試,Xcode Bots 可以存放在服務器端。
我承認我自己并沒有用過 Xcode Bots,但是我獲得的所有反饋都告訴我這個東西并不好用。
總結下,如今缺乏好奇心的開發者和大公司,可以只使用蘋果的技術,組建一整套運行在CI的單元測試和驗收測試。用于工作基本上是足夠了。
如果你正在閱讀本文,你可能充滿了好奇心,那么讓我們繼續看看開源社區有那些資源。
iOS 和 OS X 開源社區充滿了各種大牛和有趣的項目。在寫本文的時候,在 pod 上一共有 8625 個開源項目。
這些單元測試的開源庫主要都是行為描述風格(xSpec),一定程度上也反映了測試風格的一種趨勢,這風格來自于 Ruby 測試庫的 RSpec, 主要是測試類的行為,而不是枚舉方法。
Kiwi 是一個全棧式的,XCTest的代替品,支持行為描述句式。實例代碼如下
1 2 3 4 5 6 7 8 9 10 11 12 13 | describe(@"Team", ^{ context(@"when newly created", ^{ it(@"has a name", ^{ id team = [Team team]; [[team.name should] equal:@"Black Hawks"]; }); it(@"has 11 players", ^{ id team = [Team team]; [[[team should] have:11] players]; }); }); }); |
Kiwi 測試用例通常非常容易閱讀和理解代碼所想要測試的內容,他就像一個好的說明文檔。
Kiwi 集成了一些測試方法 期望(expectations), 模擬對象 (mock),樁程序 (stub),甚至還支持異步測試。
Specter 跟 Kiwi 非常像,但是它使用了不同的架構。Kiwi 是龐大的代替品 ,Specta 優勢則體現在模塊化與組件化。這個庫關心的唯一事情是編寫和運行 xSpec 風格的測試,然后用戶可以根據使用期望(expectations), 匹配(matching),模擬對象(mock)和樁程序(stub)的情況來補充相應模塊。
我個人更喜歡這個庫的設計,輕量級,包含的多個模塊可以被結合在一起。
這是 Specta 行為描述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | SpecBegin(Thing) describe(@"Thing", ^{ it(@"should do stuff", ^{ // This is an example block. Place your assertions here. }); it(@"should do some stuff asynchronously", ^{ waitUntil(^(DoneCallback done) { // Async example blocks need to invoke done() callback. done(); }); }); }); |
注意 it 執行的時候 blocks 是空的。留給庫的使用者來用他們喜歡的工具填寫。
說到工具,這里有一個庫名單,他們都可以與 Specta 和 Kiwi 配合使用:
Expecta
a matcher framework, expect(foo).to.equal(bar).OCHamcrest
another matcher framework, assertThat(foo, equalTo(bar)).OCMock
a mocking framework.OCMockito
another mocking framework.OHTTPStubs
a library to stub network requests, with block based syntax to match URLs.Nocilla
another library to stub network requests, with a nice chain-able API, stubRequest(@”POST”, ).withHeaders(…).withBody(…).Quick 是一個新的測試框架,也相當炫酷的一個。主要代碼都是用 Swift 寫的,非常適合用新的語言寫測試組件。
1 2 3 4 5 6 7 8 9 10 11 | import Quick class ThingSpec: QuickSpec { override func spec() { describe("a 'Thing'") { it("should do stuff) { // } } } } |
多虧了 Swift 的語法和閉包,Quick 的行為描述看起來比 Kiwi 和 Specta 的可讀性更強。
和 Qucik 一起的 Nimble 是一個 matcher 庫,它允許用戶進行簡潔地表達,例如 expect(10) > 2
。
無論是 Objective-C 還是 Switf,單個龐大框架或是你喜歡的庫組成的組件,開源社區提供了大量有價值的測試框架,特別是專注于寫簡潔測試的,感謝有表達句法(expressive syntax)。
蘋果提供的官方工具中單元測試框架和驗收測試框架的質量對比也反應在開源社區中??赡苁且驗?XCTest 為開源單元測試框架們提供了一個堅實的基礎,而 UIAutomation 沒有,所以我們只能選擇一些非常規的方法。
KIF,保持函數式(Keep It Functional),這是一個用 Objective-C 寫的框架,讓我們使用 XCTest 編寫驗收測試,然后在 Xcode 運行,方式和我們在單元測試做的一樣。
KIF 使用私有的 API 來獲得視圖層級,然后讓我們使用 accessibility 標簽值來視圖查詢與交互。
1 2 3 4 5 6 7 8 | - (void)testSuccessfulLogin { [tester enterText:@"user@example.com" intoViewWithAccessibilityLabel:@"Login User Name"]; [tester enterText:@"thisismypassWord" intoViewWithAccessibilityLabel:@"Login Password"]; [tester tapViewWithAccessibilityLabel:@"Log In"]; // Verify that the login succeeded [tester waitForTappableViewWithAccessibilityLabel:@"Welcome"]; } |
KIF比較不好的地方在于作者響應時間較慢。這不是批判,畢竟開源世界一切都是免費的,但我們都要賺錢糊口,可以理解作者用在這些項目上的時間是有限的。但是當整個框架的基礎都非常難以使用,那么他的穩定性一定很低。
Subliminal 是一個類似 KIF 的 Objective-C 框架,集成了 XCTest。和 KIF 不同的是,SUbliminal 是寫在 UIAutomation 上層,旨在為開發者隱藏它的復雜性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | - (void)testLogInSucceedsWithUsernameAndPassword { SLTextField *usernameField = [SLTextField elementWithAccessibilityLabel:@"username field"]; SLTextField *passwordField = [SLTextField elementWithAccessibilityLabel:@"password field" isSecure:YES]; SLElement *submitButton = [SLElement elementWithAccessibilityLabel:@"Submit"]; SLElement *loginSpinner = [SLElement elementWithAccessibilityLabel:@"Logging in..."]; NSString *username = @"Jeff", *password = @"foo"; [usernameField setText:username]; [passwordField setText:password]; [submitButton tap]; // wait for the login spinner to disappear SLAssertTrueWithTimeout([loginSpinner isInvalidOrInvisible], 3.0, @"Log-in was not successful."); NSString *successMessage = [NSString stringWithFormat:@"Hello, %@!", username]; SLAssertTrue([[SLElement elementWithAccessibilityLabel:successMessage] isValid], @"Log-in did not succeed."); // Check the internal state of the app. SLAssertTrue(SLAskAppYesNo(isUserLoggedIn), @"User is not logged in.") } |
Subliminal 聲明它可以測試應用內購警告,甚至能使 app 進入睡眠。這聽起來很牛,但事實是,在我寫本文的時候,該庫最近的一次代碼提交是 2014年9月,而且還有 13 活躍的 pull request,這些都是不好的信號。
目前我們所說到的工具中,Calabash 是最原始的一個。它是一個 Ruby 包,使用 Cucumber 編寫 BDD 風格的驗收測試,現在由 Xamarin 維護。Xamarin 是一個用 C# 寫 iOS 和 Android 應用的框架。語言會不會有點多!
不像 KIF 和 Subliminal,Calabash 完全不集成在 Xcode 中。我創建示例使用的是 Vim 和 Rake。
我們書寫 Cucumber 特性,執行每一步,然后使用命令行測試。為了它能夠工作,需要在應用內嵌入一個 HTTP 服務器,用于查詢和驅動 UI。
不用說,這可能是一個很大坑。
Cucumber/Calabash 測試代碼差不多是這樣:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | # rating_a_stand.feature Feature: Rating a stand Scenario: Find and rate a stand from the list Given I am on the foodstand list Then I should see a "rating" button And I should not see "Dixie Burger & Gumbo Soup" # steps.rb Given(/^I am on the foodstand list$/) do wait_for_element_exists "view marked:'Foodstand'" end Given(/^I should see a "([^"]*)" button$/) do |button_title| wait_for_element_exists "button marked:'#{button_title}'" end Given(/^I should not see "([^"]*)"$/) do |view_label| wait_for_element_does_not_exists "view marked:'#{view_label}' end |
Calabash 好的地方在于它是一種陳述式的測試,管理層會喜歡如果他們會讀到這些測試的話。而且它可以兼容兩個平臺。
另一方面,工具鏈并不是非常強大。測試運行相對較慢,需要在 Cucumer,Ruby,Objective 之間持續交換,消耗相當多的時間。
就像單元測試,開源庫提供了不同的選擇,用于改進你的工作流。唯一不同的是這些工具沒那么成熟,社區沒那么活躍。
為我們的項目套上好的測試工具,其最后一步是擁有持續集成。在開發者機器上運行測試并不能保證代碼不會出錯,畢竟其他團隊成員會對代碼進行更改。有個人來不斷運行測試會更加安全。
不用說,最好的 CI 是在云端進行。配置維護一套 Jenkins 需要大量的時間。
CI 的選擇會更多。這里列出一些支持 iOS 項目的主要 CI 服務。
它們之間的區別主要是在價格,上手容易程度,以及如何配置。例如 Travis CI 使用 .travis.yml
文件定義所有的步驟,而 Bitrise 則圖形界面,每個步驟都用 block 展示,并且這block可以被添加到進程。
上面這個列表可能并不全面,我可能落了一些。希望這個對于有興趣寫測試和 CI 的人是一個好的開始。
新聞熱點
疑難解答