iOS單元測試:Specta + Expecta + OCMock + OHHTTPStubs + KIF


框架選擇

參考這篇選型文章, 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做一個基礎的了解還是有必要的。
參考:


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的過程



參考:

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"}];
}];
這個框架的主要用法就是上面這個演示樣例,用法非常明顯易用。結合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");
  }];
}

因為NSURLProtocol的局限性。OHHTTPStubs沒法用來測試background sessions和模擬數據上傳。


F.I.R.S.T 原則

  • Fast — 測試應該可以被常常執行
  • Isolated — 測試本身不能依賴於外部因素或其它測試的結果
  • Repeatable — 每次執行測試都應該產生同樣的結果
  • Self-verifying — 測試應該依賴於斷言,不須要人為干預
  • Timely — 測試應該和生產代碼一同書寫
怎樣將測試結果收益最大化:不要將測試和實現細節耦合在一起。
  • 不要測試私有方法
  • 不要Stub私有方法
  • 不要Stub外部庫
  • 正確地Stub依賴
  • 不要測試構造函數

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM