假設有測試大佬發現內容不正確。歡迎指正,我會及時改動。css
大多數的iOS App(沒有持續集成)迭代流程是這種html
也就是說。測試是公佈以前的最後一道關卡。假設bug不能在測試中發現,那麼bug
就會抵達用戶,因此測試的完整性和可靠性十分重要。android
眼下,大多數App還停留在人工測試階段,人工測試投入的成本最低,可以保證核心功能的使用,並且測試人員不需要會寫代碼。ios
但是,在很是多測試場景下。人工測試的效率過低,easy出錯。git
舉兩個常見的樣例:github
上線了以後,後臺配置數據的時候出了點小問題,致使大面積崩潰,boom~。web
而後,老闆就要過來找你了sql
本文所解說的均是基於XCode 8.2.1,有些概念可能不適用於低版本號的XCode編程
本身主動化測試就是寫一些測試代碼。用代碼取代人工去完畢模塊和業務的測試。swift
事實上不管是開發仍是測試,假設你在不斷的作反覆性工做的時候。就應該問本身一個問題:是否是有更高效的辦法?
本身主動化測試有很是多長處:
固然,本身主動化測試也有一些缺點。
因此。在作本身主動化測試以前,首先要問本身幾個問題?
一般,咱們會選擇那些業務穩定。需要頻繁測試的部分來編寫本身主動化測試腳本,其他的採用人工測試。人工測試仍然是iOS App開發中必不可少的一部分。
從是否接觸源碼的角度來分類:測試分爲黑盒和白盒(灰盒就是黑盒白盒結合,這裏不作討論)。
白盒測試的時候,測試人員是可以直接接觸待測試App的源碼的。
白盒測試不少其它的是單元測試,測試人員針對各個單元進行各類可能的輸入分析,而後測試其輸出。白盒測試的測試代碼一般由iOS開發編寫。
黑盒測試。
黑盒測試的時候,測試人員不需要接觸源碼。
是從App層面對其行爲以及UI的正確性進行驗證。黑盒測試由iOS測試完畢。
從業務的層次上來講,測試金字塔如圖:
而iOS測試一般僅僅有下面兩個層次:
囉裏八嗦講的這麼多,本身主動化測試的效率怎麼樣。關鍵仍是在測試框架上。
那麼,怎樣選擇測試框架呢?框架可以分爲兩大類:XCode內置的和三方庫。
選擇框架的時候有幾個方面要考慮
咱們首先來看看XCode內置的框架:XCTest。XCTest又可以分爲兩部分:Unit Test 和 UI Test,分別相應單元測試和UI測試。有一些三方的測試庫也是基於XCTest框架的,這個在後文會講到。由於是Apple官方提供的。因此這個框架會不斷無缺。
成熟的三方框架一般提供了很是多封裝好的有好的接口。筆者綜合對照了一些,推薦下面框架:
單元測試:
下面三個框架都是BDD(Behavior-driven development) - 行爲驅動開發。行爲驅動開發簡單來講就是先定義行爲。而後定義測試用例,接着再編寫代碼。 實踐中發現。一般沒有那麼多時間來先定義行爲。只是BDD中的domain-specific language (DSL)可以很是好的描寫敘述用例的行爲。
UI測試
App至關於一個Server,測試代碼至關於Client,經過發送JSON來操做APP,測試語言可以是隨意的,支持android和iOS。
篇幅有限。本文會先介紹XCtest。接着三方的Unit框架會以Quick爲例,UI Test框架側重分析KIF,appium僅僅作原理解說。
對於XCTest來講。最後生成的是一個bundle。
bundle是不能直接運行的,必須依賴於一個宿主進程。關於XCTest進行單元測試的基礎(XCode的使用,異步測試。性能測試,代碼覆蓋率等)。我在這篇文章裏解說過,這裏再也不具體解說。
比方,我有下面一個函數:
//驗證一段Text是否有效。(不能以空字符開頭,不能爲空) - (BOOL)validText:(NSString *)text error:(NSError *__autoreleasing *)error{ }
那麼。我該怎樣爲這個函數編寫單元測試的代碼?一般,需要考慮下面用例:
UI測試是模擬用戶操做。進而從業務處層面測試。關於XCTest的UI測試,建議看看WWDC 2015的這個視頻:
關於UI測試,有幾個核心類需要掌握
UI測試另外一個核心功能是UI Recording。
選中一個UI測試用例,而後點擊圖中的小紅點既可以開始UI Recoding。你會發現:
隨着點擊模擬器,本身主動合成了測試代碼。(一般本身主動合成代碼後。還需要手動的去調整)
在寫UI測試用例的時候要注意:測試行爲而不是測試代碼。比方,咱們測試這樣一個case
進入Todo首頁,點擊add,進入加入頁面,輸入文字,點擊save。
測試效果例如如下:
相應測試代碼:
- (void)testAddNewItems{
//獲取app代理
XCUIApplication *app = [[XCUIApplication alloc] init];
//找到第一個tabeview,就是咱們想要的tableview
XCUIElement * table = [app.tables elementBoundByIndex:0];
//記錄下來加入以前的數量
NSInteger oldCount = table.cells.count;
//點擊Add
[app.navigationBars[@"ToDo"].buttons[@"Add"] tap];
//找到Textfield
XCUIElement *inputWhatYouWantTodoTextField = app.textFields[@"Input what you want todo"];
//點擊Textfield
[inputWhatYouWantTodoTextField tap];
//輸入字符
[inputWhatYouWantTodoTextField typeText:@"somethingtodo"];
//點擊保存
[app.navigationBars[@"Add"].buttons[@"Save"] tap];
//獲取當前的數量
NSInteger newCount = table.cells.count;
//假設cells的數量加一,則以爲測試成功
XCTAssert(newCount == oldCount + 1);
}
這裏是經過先後tableview的row數量來斷言成功或者失敗。
一般,在視圖切換的時候有轉場動畫,咱們需要等待動畫結束,而後才幹繼續。不然query的時候很是可能找不到咱們想要的控件。
比方。例如如下代碼等待VC轉場結束,當query僅僅有一個table的時候,才繼續運行興許的代碼。
[self expectationForPredicate:[NSPredicate predicateWithFormat:@"self.count = 1"]
evaluatedWithObject:app.tables
handler:nil];
[self waitForExpectationsWithTimeout:2.0 handler:nil];
//興許代碼....
Tips: 當你的UI結構比較複雜的時候。比方各類嵌套childViewController,使用XCUIElementQuery的代碼會很是長。也很差維護。
另外,UI測試還會在每一步操做的時候截圖。方便對測試報告進行驗證。
使用基於XCTest的框架,可以在XCode的report navigator中查看測試結果。
當中:
除了利用XCode的GUI,還可以經過後文提到的命令行工具來測試。查看結果。
首先解釋兩個術語:
一般,假設你採用純存的XCTest。推薦採用OCMock來實現mock和stub,單元測試的三方庫一般已集成了stub和mock。
那麼,怎樣使用mock呢?舉個官方的樣例:
//mock一個NSUserDefaults對象
id userDefaultsMock = OCMClassMock([NSUserDefaults class]);
//在調用stringForKey的時候,返回http://testurl
OCMStub([userDefaultsMock
stringForKey:@"MyAppURLKey"]).andReturn(@"http://testurl");
再比方,咱們要測試打開其它App,那麼怎樣推斷確實打開了其它App呢?
id app = OCMClassMock([UIApplication class]);
OCMStub([app sharedInstance]).andReturn(app);
OCMVerify([app openURL:url]
使用Stub可以讓咱們很是方便的實現這個。
關於OCMock的使用。推薦看看objc.io的這篇文章
Quick是創建在XCTestSuite上的框架,使用XCTestSuite贊成你動態建立測試用例。因此,使用Quick,你仍讓可以使用XCode的測試相關GUI和命令行工具。
使用Quick編寫的測試用例看起來是這樣子的:
import Quick
import Nimble
class TableOfContentsSpec: QuickSpec {
override func spec() {
describe("the 'Documentation' directory") {
it("has everything you need to get started") {
let sections = Directory("Documentation").sections
expect(sections).to(contain("Organized Tests with Quick Examples and Example Groups"))
expect(sections).to(contain("Installing Quick"))
}
context("if it doesn't have what you're looking for") {
it("needs to be updated") {
let you = You(awesome: true)
expect{you.submittedAnIssue}.toEventually(beTruthy())
}
}
}
}
}
BDD的框架讓測試用例的目的更加明白,測試是否經過更加清晰。使用Quick,測試用例分爲兩種:
it有兩個參數。
比方,下面測試Dolphin行爲。它具備行爲is friendly
和is smart
//Swift代碼
class DolphinSpec: QuickSpec {
override func spec() {
it("is friendly") {
expect(Dolphin().isFriendly).to(beTruthy())
}
it("is smart") {
expect(Dolphin().isSmart).to(beTruthy())
}
}
}
可以看到。BDD的核心是行爲。也就是說,需要關注的是一個類提供哪些行爲。
比方。驗證dolphin的click行爲的時候。咱們需要兩個用例。一個是is loud
,一個是has a high frequency
,就可以用describe將用例組織起來。
class DolphinSpec: QuickSpec {
override func spec() {
describe("a dolphin") {
describe("its click") {
it("is loud") {
let click = Dolphin().click()
expect(click.isLoud).to(beTruthy())
}
it("has a high frequency") {
let click = Dolphin().click()
expect(click.hasHighFrequency).to(beTruthy())
}
}
}
}
}
context可以指定用例的條件:
比方
describe("its click") {
context("when the dolphin is not near anything interesting") {
it("is only emitted once") {
expect(dolphin!.click().count).to(equal(1))
}
}
}
除了這些以外,Quick也支持一些切入點。進行測試前的配置:
由於Quick是基於XCTest,開發人員固然可以收使用斷言來定義測試用例成功或者失敗。Quick提供了一個更有好的Framework來進行這種斷言:Nimble
比方,一個常見的XCTest斷言例如如下:
XCTAssertTrue(ConditionCode, "FailReason")
在出錯的時候,會提示
XCAssertTrue failed, balabala
這時候,開發人員要打個斷點。查看下上下文。看看具體失敗的緣由在哪。
使用Nimble後,斷言變成相似
expect(1 + 1).to(equal(2)) expect(3) > 2 expect("seahorse").to(contain("sea")) expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))
並且。出錯的時候,提示信息會帶着上下文的值信息。讓開發人員更easy的找到錯誤。
測試的準確性和工做量很是大程度上依賴於開發人員的代碼質量。
一般。爲了單元測試的準確性,咱們在寫函數(方法)的時候會借鑑一些函數式編程的思想。
當中最重要的一個思想就是
何爲Pure function?就是假設一個函數的輸入同樣,那麼輸出必定同樣。
比方。這種一個函數就不是pure function。由於它依賴於外部變量value的值。
static NSInteger value = 0;
- (NSInteger)function_1{
value = value + 1;
return value;
}
而這個函數就是pure function,由於給定輸入,輸出必定一致。
- (NSInteger)function_2:(NSInteger)base{
NSInteger value = base + 1;
return value;
}
因此。假設你寫了一個沒有參數,或者沒有返回值的方法,那麼你要當心了。很是可能這種方法很是難測試。
在良好的MVC架構的App中。
因此,對Controller瘦身是iOS架構中比較重要的一環。一些通用的技巧包含:
邏輯抽離:
可以每一個網絡請求以Command模式封裝成一個對象,不要直接在Controller調用AFNetworking。
比方登陸狀態等等,經過服務去訪問。這樣服務提供者之需要處理服務的質量,服務使用者則信任服務提供者的結果。
Controller與View解耦合
Controller與Controller解耦合
假設你的App用Swift開發。那麼面向協議編程和不可變的值類型會讓你的代碼更easy測試。
固然,iOS組建化對本身主動化測試的幫助也很是大,由於不管是基礎組件仍是業務組件,都可以獨立測試。組建化又是一個很是大的課題,這裏不深刻解說了。
KIF的全稱是Keep it functional。
它是一個創建在XCTest的UI測試框架,經過accessibility來定位具體的控件。再利用私有的API來操做UI。由於是創建在XCTest上的,因此你可以完美的藉助XCode的測試相關工具(包含命令行腳本)。
> KIF是我的很是推薦的一個框架,簡單易用。
使用KIF框架強制要求你的代碼支持accessibility。假設你以前沒接觸過,可以看看Apple的文檔
簡單來講。accessibility可以讓視覺障礙人士使用你的App。
每一個控件都有一個描寫敘述AccessibilityLabel。
在開啓VoiceOver的時候,點擊控件就可以選中並且聽到相應的描寫敘述。
一般UIKit的控件是支持accessibility的。自定定義控件可以經過代碼或者Storyboard上設置。
在Storyboard上設置:
經過代碼設置:
[alert setAccessibilityLabel:@"Label"];
[alert setAccessibilityValue:@"Value"];
[alert setAccessibilityTraits:UIAccessibilityTraitButton];
假設你有些Accessibility的經驗。那麼你確定知道。像TableView的這種不該該支持VoiceOver的。咱們可以用條件編譯來僅僅對測試Target進行設置:
#ifdef DEBUG
[tableView setAccessibilityValue:@"Main List Table"];
#endif
#ifdef KIF_TARGET (這個值需要在build settings裏設置)
[tableView setAccessibilityValue:@"Main List Table"];
#endif
使用KIF主要有兩個核心類:
咱們用KIF來測試加入一個新的ToDo
- (void)testAddANewItem{
[tester tapViewWithAccessibilityLabel:@"Add"];
[tester enterText:@"Create a test to do item" intoViewWithAccessibilityLabel:@"Input what you want todo"]; [tester tapViewWithAccessibilityLabel:@"Save"]; [tester waitForTimeInterval:0.2]; [tester waitForViewWithAccessibilityLabel:@"Create a test to do item"]; }
本身主動化測試中,命令行工具可以facebook的開源項目:
這是一個基於xcodebuild
命令的擴展,在iOS本身主動化測試和持續集成領域很是實用,並且它支持-parallelize
並行測試多個bundle,大大提升測試效率。
安裝XCTool,
brew install xctool
使用
path/to/xctool.sh \
-workspace YourWorkspace.xcworkspace \
-scheme YourScheme \
-reporter plain:/path/to/plain-output.txt \
run-test
並且。xctool對於持續集成很是實用,iOS常用的持續集成的server有兩個:
一般,你的你的測試用例分爲三部分:
當測試用例多了。你會發現測試代碼編寫和維護也是一個技術活。
一般。咱們會從幾個角度考慮:
一個常見的測試代碼組織例如如下:
appium採用了Client Server的模式。對於App來講就是一個Server,基於WebDriver JSON wire protocol對實際的UI操做庫進行了封裝,並且暴露出RESTFUL的接口。
而後測試代碼經過HTTP請求的方式,來進行實際的測試。
當中,實際驅動UI的框架依據系統版本號有所不一樣:
緣由也比較簡單:Apple在10.0以後。移除了UIAutomation的支持,僅僅支持XCUITest。
對照KIF。appium有它的長處:
固然,不論什麼框架都有缺點:
由於我不是專業的iOS測試,關於測試的一點看法例如如下:
聲明:本博客所有文章均爲我的觀點。與僱主沒有不論什麼關係。