說(shuō)真的务荆,自己在做客戶(hù)端開(kāi)發(fā)的時(shí)候基本沒(méi)有寫(xiě)過(guò)單元測(cè)試。????穷遂,不是對(duì)自己技術(shù)有多自信函匕,而是因?yàn)榛久Σ贿^(guò)來(lái),加上上頭沒(méi)明確要求蚪黑,就得過(guò)且過(guò)了盅惜。不過(guò)作為一個(gè)比較合格的攻城獅中剩,對(duì)單元測(cè)試還是需要了解的。今天總結(jié)一下在開(kāi)發(fā)iOS&OSX中從蘋(píng)果自家的
XCTest
到牛的一比的第三方開(kāi)源測(cè)試框架抒寂。由于東西多结啼,一時(shí)半會(huì)弄不完,只做簡(jiǎn)單入門(mén)介紹屈芜。后續(xù)會(huì)一一介紹郊愧。
從Xcode談起
蘋(píng)果在Xcode5
中推出了一個(gè)簡(jiǎn)單而有效的框架--XCTest用于做單元測(cè)試。XCTest
是以xUnit way
的方式寫(xiě)的井佑。
寫(xiě)XCTest
測(cè)試用例非常簡(jiǎn)單属铁,而且開(kāi)發(fā)者通過(guò)IDE點(diǎn)擊?U
可以非常迅速的獲取Xcode
的反饋信息。同時(shí)Xcode
也有一個(gè)專(zhuān)門(mén)用戶(hù)測(cè)試的區(qū)域毅糟,如下圖红选,在這里我們可以看到所有的測(cè)試用例成功或者失敗的情況。
通過(guò)的測(cè)試用例用綠色標(biāo)識(shí)姆另,未通過(guò)的測(cè)試用例用紅色標(biāo)識(shí)喇肋。
雖然XCTest
和Xcode
集成在一起非常方便,非常容易使用迹辐,但是也有一些問(wèn)題蝶防,比如XCTAssert...
API不是那么的有表現(xiàn)力和通用。尤其拋開(kāi)了Xcode
跑XCTest
會(huì)比較麻煩明吩。
這里有篇介紹的不錯(cuò)的入門(mén)介紹:
有精力的還是推薦看
隨著技術(shù)發(fā)展间学,為了更為方便的寫(xiě)UI測(cè)試。蘋(píng)果t提供了UI測(cè)試框架--UIAutomation印荔。UIAutomation通過(guò)JS來(lái)寫(xiě)測(cè)試低葫,并且允許開(kāi)發(fā)者驅(qū)動(dòng)應(yīng)用程序的UI并且在不同的狀態(tài)插入斷言。盡管這聽(tīng)說(shuō)起來(lái)有點(diǎn)牛逼仍律,但是真真的通過(guò)UIAutomation
來(lái)寫(xiě)測(cè)試用例嘿悬,結(jié)果會(huì)變得非常糟糕。并且JS的API沒(méi)有原生的單元測(cè)試API強(qiáng)大水泉。
來(lái)看看JS寫(xiě)的:
UIATarget.localTarget().frontMostApp().navigationBar().buttons()["Add"].tap();
UIATarget.localTarget().frontMostApp().mainWindow().tableViews()[0].cells()[0].elements()["Chocolate Cake"];
UIATarget.localTarget().frontMostApp().mainWindow().tableViews()[0].scrollToElementWithPredicate("name beginswith 'Turtle Pie'");
正如你所見(jiàn)善涨,麻蛋居然比我寫(xiě)OC的代碼還冗長(zhǎng)。并且必須使用Instruments
來(lái)運(yùn)行草则「峙。總的來(lái)說(shuō)就是不爽。蘋(píng)果推出的最新的方案是Xcode Bot,舉個(gè)例子炕横,可以通過(guò)安裝Xcode Server來(lái)自動(dòng)跑測(cè)試用例源内。更加具體的內(nèi)容可以看看看官方文檔。
以上是蘋(píng)果自家的方案份殿。
測(cè)試相關(guān)的開(kāi)源庫(kù)
關(guān)于iOS和OSX的開(kāi)源項(xiàng)目還是有很多的姿锭。就單單來(lái)講塔鳍,收錄到Cocoapods
的項(xiàng)目而言。通過(guò)Pod list
就可以知道有多少個(gè)了呻此,我本機(jī)試了一下,一共有24173 pods were found
腔寡。在這么多的第三方中焚鲜,有一些非常不錯(cuò)的開(kāi)發(fā)測(cè)試框架。
單元測(cè)試的開(kāi)源庫(kù)大致都遵循了一種測(cè)試風(fēng)格放前,事實(shí)上就是xSpec style,
這種風(fēng)格來(lái)至于Ruby測(cè)試庫(kù)RSpec.提供了測(cè)試類(lèi)更多的操作忿磅,而不僅僅只是枚舉方法。
Kiwi
kiwi是一個(gè)行為驅(qū)動(dòng)開(kāi)發(fā)庫(kù)凭语,可以完全替代XCTest
,語(yǔ)言用得是OC,所以開(kāi)發(fā)上手也比較快葱她。來(lái)看看具體怎么寫(xiě):
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規(guī)范更容易閱讀和溝通。而且在它的wiki里面寫(xiě)的非常詳細(xì)似扔。并且有自己的一套規(guī)范吨些。有如下內(nèi)容:
- Specs:
- Expectations:
- Mocks and Stubs:
- Asynchronous Testing:
具體請(qǐng)看kiwi-bdd/Kiwi
Specta
specta是一個(gè)輕量級(jí)的TDD/BDD的測(cè)試框架,同樣適用OC語(yǔ)言編寫(xiě)炒辉。相對(duì)于kiwi而言豪墅,kiwi更為龐大,specta更為輕量級(jí)黔寇、組件化偶器。
示例代碼如下:
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();
});
});
});
specta有一些依賴(lài)的工具,可以根據(jù)需要添加缝裤。比如:
target :MyApp do
# your app dependencies
target :MyAppTests do
inherit! :search_paths
pod 'Specta', '~> 1.0'
# pod 'Expecta', '~> 1.0' # expecta matchers
# pod 'OCMock', '~> 2.2' # OCMock
# pod 'OCHamcrest', '~> 3.0' # hamcrest matchers
# pod 'OCMockito', '~> 1.0' # OCMock
# pod 'LRMocky', '~> 0.9' # LRMocky
end
end
Quick
quick相對(duì)而言比較年輕屏轰,適用Swift語(yǔ)言編寫(xiě)的,但是目前star數(shù)卻高于前面介紹的框架憋飞。隨便說(shuō)一下霎苗,quick依賴(lài)一個(gè)匹配框架Nimble
,彌補(bǔ)了XCTAssert()
使用起來(lái)不夠明確搀崭,清晰的缺點(diǎn)叨粘。
感受一下Nimble
:
// Swift
expect(1 + 1).to(equal(2))
expect(1.2).to(beCloseTo(1.1, within: 0.1))
expect(3) > 2
expect("seahorse").to(contain("sea"))
expect(["Atlantic", "Pacific"]).toNot(contain("Mississippi"))
expect(ocean.isClean).toEventually(beTruthy())
是不是比XCTAssert()
用起來(lái)舒服一些。
得益于swift語(yǔ)法及閉包瘤睹,相對(duì)于之前介紹的框架升敲,Quick的可讀性更好。例子如下:
// Swift
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())
}
}
}
}
}
用于UI測(cè)試的開(kāi)源庫(kù)
相對(duì)于XCTest
而言轰传,由于蘋(píng)果排斥第三方工具關(guān)于UI測(cè)試方面只有UIAutomation
驴党。剩下的方案只有hack了。
KIF
kif展開(kāi)其實(shí)就是keep it functional
,用OC寫(xiě)获茬,基于XCTexst
港庄。
KIF的tester
使用私有API訪問(wèn)視圖層次倔既,并通過(guò)視圖的accessiblity label value
進(jìn)行交互。
示例代碼如下:
- (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需要被加入到項(xiàng)目中的Unit Test target
而不是UI Test target
鹏氧。
總的來(lái)說(shuō)是目前比較優(yōu)秀的iOS開(kāi)源庫(kù)中UI 測(cè)試框架渤涌。
這里有一篇美團(tuán)分享的關(guān)于它的使用。詳細(xì)介紹請(qǐng)看基于 KIF 的 iOS UI 自動(dòng)化測(cè)試和持續(xù)集成
Subliminal
sublimnial是一個(gè)OC框架把还,和KIF對(duì)比而言实蓬,二者都是集成了XCTest
,不同之處是sublimnial實(shí)在UIAtomation
基礎(chǔ)上實(shí)現(xiàn)的,而KIF是利用 蘋(píng)果 給所有控件提供的輔助屬性 accessibility attributes
來(lái)定位和獲取元素吊履,完成界面的交互操作安皱;結(jié)合使用 Xcode 的 XCTest 測(cè)試框架,擁有 XCTest 測(cè)試框架的特性艇炎,使得測(cè)試用例能以 command line build 工具運(yùn)行并獲取測(cè)試報(bào)告酌伊。
示例代碼:
- (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.")
}
不幸的是這個(gè)庫(kù)已經(jīng)很久很久沒(méi)有維護(hù)了。
Calabash
上面介紹的框架都是針對(duì)于iOS或者OSX缀踪,Calabash
是一個(gè)跨平臺(tái)的測(cè)試框架居砖。Xamarin維護(hù)這種鴿項(xiàng)目,可能有些同學(xué)對(duì)Xamarin不是很熟悉辜贵,這個(gè)公司主要使用C#來(lái)寫(xiě)iOS和Android應(yīng)用程序悯蝉。
不像KIF和Subliminal不用和Xcode集成,但是寫(xiě)方法就和OC有比較大的區(qū)別托慨。因?yàn)?code>Calabash是基于Ruby的鼻由。
示例代碼:
# 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
目前本人還沒(méi)有用過(guò)這個(gè)牛逼的測(cè)試框架。如果有人感興趣可以嘗試一下厚棵。Calabash官網(wǎng)
聊聊其他
上面粗略的介紹了iOS或者OSX官方自帶及開(kāi)源的測(cè)試框架蕉世。只是起到了入門(mén)簡(jiǎn)單介紹的作用。讀者完全可以自己實(shí)踐體驗(yàn)下一下各個(gè)框架的優(yōu)劣婆硬。
聊了下測(cè)試相關(guān)的狠轻,如果再繼續(xù)深入。就是持續(xù)集成彬犯,持續(xù)部署這些了向楼。這塊網(wǎng)上也有不少文章介紹的。