框架選擇
參考這篇選型文章,
http://zixun.github.io/blog/2015/04/11/iosdan-yuan-ce-shi-xi-lie-dan-yuan-ce-shi-kuang-jia-xuan-xing/,盡管結論不一定全然適用,可是關於框架對照的地方還是值得閱讀的。基於這篇文章,排除Kiwi框架之后,決定參考一些項目的源碼,了解他們使用的測試方面的框架。
首先,參考
https://github.com/artsy/eigen開源項目,其內部總體結構很完整,開發流程也很專業。至少比我知道的大多數國內團隊都要專業:
eigen: Specta + OCMock + Expecta + OHHTTPStubs + FBSnapshotTestCase + "Expecta+Snapshots" + "XCTest+OHHTTPStubSuiteCleanUp”。
其次,參考公司內部別的項目使用情況。發現使用下面框架來做測試方面的事情: Specta + Expecta + OCMock + OHTTPStubs + KIF(UI Test)
so,我決定選擇 Specta (BDD框架) + Expecta(斷言框架) + OCMock(mock框架) + OHHTTPStubs(http stub框架) + KIF(UI Test) 做測試框架來學習。
XCTest簡單介紹
因為我決定不直接使用XCTest作為測試框架。可是又因為Specta是基於XCTest進行封裝的,所以對XCTest做一個基礎的了解還是有必要的。
參考:
1.
https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/testing_with_xcode
3.
http://zixun.github.io/blog/2015/04/16/iosdan-yuan-ce-shi-xi-lie-dan-yuan-ce-shi-bian-ma-gui-fan/
BDD框架 — Specta
1. 簡單介紹
眼下主流的BDD框架,這些BDD框架在語法層面差點兒是同樣的,基本的差別在於他們的可配置能力和綁定的組件。
以下三個OC BDD框架相對於官方框架XCTest都具有更好的可讀性。另外如今已經有了比較流行的swift BDD框架: https://github.com/railsware/Sleipnir 和 https://github.com/Quick/Quick。
關於specta與kiwi框架的對照,參考:
http://appleprogramming.com/blog/2014/01/18/tdd-with-specta-and-expecta/,這篇文章的結論是specta相對於kiwi有更加優雅的語法,對於我這樣的剛開始使用的新手,果斷採用specta這樣的各種完勝的框架。Specta框架具有一下特點:
- An OC RSpec-like BDD DSL
- Quick and easy set up
- Build on top of XCTest
- Excellent Xcode integration
2. Specta BDD DSL語法簡單介紹
能夠參考
https://github.com/specta/specta 官網和
https://github.com/artsy/eigen項目中的test case代碼來學習語法
1) SpecBegin 聲明了一個測試類。SpecEnd 結束了類聲明
2) describe (context) 塊聲明了一組實例
3) it (example/specify) 是一個單一的樣例
4) beforeAll 是一個執行於全部同級塊之前的塊,僅僅執行一次。afterAll 與beforeAll相反,是在全部同級塊之后執行的塊。僅僅執行一次。
5) beforeEach/afterEach,在每一個同級塊執行的時候,都會執行一次,而beforeAll/afterAll僅僅會執行一次
6) it/waitUntil/done()。異步調用,注意完畢異步操作之后。必須調用done()函數。例如以下:
-
it( @"should do some stuff asynchronously", ^{
waitUntil(^(DoneCallback done) {
// Async example blocks need to invoke done() callback.
done();
});
});
7) sharedExamplesFor 和 itShouldBehaveLike結合在一起。能夠實如今不同的spec之間共享同一套test case,參考:
http://artandlogic.com/2014/02/specta-shared-behavior/;sharedExamplesFor 設置多個spec之間共享的test case,第一個參數作為標識符。通過itShouldBehaveLike來執行spec中test case。第一個參數傳入sharedExamplesFor設置時使用的標識符。注意。在describe局部使用sharedExamplesFor定義shared examples。能夠在它作用域內覆蓋全局的shared examples。
8) pending,僅僅打印一條log信息。不做測試。這個語句會給出一條警告,能夠作為一開始集中書寫行為描寫敘述時還未實現的測試的提示。
斷言框架 — Expecta
使用方法能夠參考開源項目:
https://github.com/artsy/eigen。從中找到相應的代碼學習是最好的方式。假設須要找到很多其它更全面的使用方法,能夠去項目官方站點:
https://github.com/specta/expecta。截取一段eigen上面代碼,基本上就能夠了解Expecta框架的基本使用方法了,例如以下圖中 expect(mapView.nextZoomScale).to.equal(mapView.annotationZoomScaleThreshold)

mock框架 — OCMock
了解OCMock 2.x中的具體features。能夠參考:
http://ocmock.org/features/;了解OCMock 3.x的具體API。能夠參考:
http://ocmock.org/reference/;
In a modern Object Oriented system, the component under test will likely have several object dependencies. Instead of instantiating dependencies as concrete classes, we use mocks. Mocks are ‘fake’ objects with pre-defined behavior to stand-in for concrete objects during testing. The component under test does not know the difference! With mocks, a component can be tested with confidence that it behaves as designed within a larger system.
OCMock框架的使用方法也比較簡單,因為我個人時間比較緊張,僅僅能抽出一兩天的時間學習測試部分的知識,就不多說了,以下幾篇文章都說的比較清楚,能夠參考:
http://zixun.github.io/blog/2015/04/16/iosdan-yuan-ce-shi-xi-lie-yi-ocmockchang-jian-shi-yong-fang-shi/ ,學習2.x和3.x的API的基本使用。
另外能夠參考開源項目 https://github.com/artsy/eigen,學習當中的OCMock API的使用,框架使用比較簡單,看看就懂了,不須要多說。
eigen的一個test case,注意在運行完成的時候,須要調用stopMocking。OCMockObject是基於runtime方法轉發實現的。mock一個對象,就是對這個對象的方法進行轉發的過程,運行完成須要調用stopMocking,否則會影響其它test case的運行。
以下能夠看出一個OCMock基本過程:獲得OCMockObject -> stub方法 -> 設置expect -> verify校驗運行結果 -> 調用stopMocking

以下有一個mock一個alert view show的過程

參考:
- http://ocmock.org/reference/
- http://ocmock.org/features/
- http://ocmock.org/introduction/
- http://www.archive.alexvollmer.com/posts/2010/06/28/making-fun-of-things-with-ocmock/
- http://hackazach.net/code/2014/03/03/effective-testing-with-ocmock/。翻譯:http://zixun.github.io/blog/2015/04/16/iosdan-yuan-ce-shi-xi-lie-yi-ocmockchang-jian-shi-yong-fang-shi/
- http://engineering.aweber.com/improving-ios-unit-tests-with-ocmock/
OHHTTPStubs
官方:
https://github.com/AliSoftware/OHHTTPStubs。這個框架是基於NSURLProtocol實現的。之前正好看過這部分的僅僅是,整理來說。這個框架的源碼並不復雜,但實現還是比較巧妙的。具體的介紹和使用,在github上面介紹的很清楚,框架本身使用也比較簡單:
[OHHTTPStubs
stubRequestsPassingTest:^
BOOL(
NSURLRequest *request) {
return [request.URL.host isEqualToString: @"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse*( NSURLRequest *request) {
// Stub it with our "wsresponse.json" stub file (which is in same bundle as self)
NSString* fixture = OHPathForFile( @"wsresponse.json", self. class);
return [OHHTTPStubsResponse responseWithFileAtPath:fixture
statusCode: 200 headers:@{ @"Content-Type": @"application/json"}];
return [request.URL.host isEqualToString: @"mywebservice.com"];
} withStubResponse:^OHHTTPStubsResponse*( NSURLRequest *request) {
// Stub it with our "wsresponse.json" stub file (which is in same bundle as self)
NSString* fixture = OHPathForFile( @"wsresponse.json", self. class);
return [OHHTTPStubsResponse responseWithFileAtPath:fixture
statusCode: 200 headers:@{ @"Content-Type": @"application/json"}];
}];
這個框架的主要用法就是上面這個演示樣例,用法非常明顯易用。結合unit test使用的時候。須要使用網絡請求的時候。能夠在it或者beforeAll或者beforeEach的時候進行stub request。即上面這段代碼的行為。可是不要忘記的是。須要在tear down的時候,即specta的afterAll的時候,記得調用 [OHHTTPStubs removeAllStubs] 。
注意,這里僅僅是使用NSURLProtocol來stub request。不會影響被測試的請求接口的測試。請求是異步的話,能夠使用Specta的it/waitUntil/done()流程對請求進行測試,假設使用XCTest的話,OHTTPStubs給出了一個wiki解決。使用XCTestExpectation來搞定。我認為挺有意思:
- (
void)testFoo
{
NSURLRequest* request = ...
XCTestExpectation* responseArrived = [ self expectationWithDescription: @"response of async request has arrived"];
__block NSData* receivedData = nil;
[ NSURLConnection sendAsynchronousRequest:request
queue:[ NSOperationQueue mainQueue]
completionHandler:^( NSURLResponse* response, NSData* data, NSError* error)
{
receivedData = data;
[responseArrived fulfill];
}
];
[ self waitForExpectationsWithTimeout:timeout handler:^{
// By the time we reach this code, the while loop has exited
// so the response has arrived or the test has timed out
XCTAssertNotNil(receivedData, @"Received data should not be nil");
}];
}
{
NSURLRequest* request = ...
XCTestExpectation* responseArrived = [ self expectationWithDescription: @"response of async request has arrived"];
__block NSData* receivedData = nil;
[ NSURLConnection sendAsynchronousRequest:request
queue:[ NSOperationQueue mainQueue]
completionHandler:^( NSURLResponse* response, NSData* data, NSError* error)
{
receivedData = data;
[responseArrived fulfill];
}
];
[ self waitForExpectationsWithTimeout:timeout handler:^{
// By the time we reach this code, the while loop has exited
// so the response has arrived or the test has timed out
XCTAssertNotNil(receivedData, @"Received data should not be nil");
}];
}
因為NSURLProtocol的局限性。OHHTTPStubs沒法用來測試background sessions和模擬數據上傳。
F.I.R.S.T 原則
- Fast — 測試應該可以被常常執行
- Isolated — 測試本身不能依賴於外部因素或其它測試的結果
- Repeatable — 每次執行測試都應該產生同樣的結果
- Self-verifying — 測試應該依賴於斷言,不須要人為干預
- Timely — 測試應該和生產代碼一同書寫
怎樣將測試結果收益最大化:不要將測試和實現細節耦合在一起。
- 不要測試私有方法
- 不要Stub私有方法
- 不要Stub外部庫
- 正確地Stub依賴
- 不要測試構造函數
參考資料
- http://www.objc.io/issues/15-testing/,(翻譯:http://objccn.io/issue-15/ )
- https://github.com/artsy/eigen,很專業的APP的開源碼,http://objccn.io/issue-22-2/
- <Functional Reactive Programming on iOS>: RAC + 單元測試
- http://www.jianshu.com/p/73f9d719cee4
- http://nshipster.com/unit-testing/
- http://onevcat.com/2014/02/ios-test-with-kiwi/
- http://onevcat.com/2014/05/kiwi-mock-stub-test/
- https://github.com/dblock/fui,find unused objective-c imports
- <Testing with Xcode>
- <Pro iOS Continuous Integration>