[iOS翻譯]《iOS7 by Tutorials》系列:在Xcode 5裡使用單元測試(上)

來源:互聯網
上載者:User

簡介:

單元測試是軟體開發的一個重要方面。畢竟,單元測試可以幫你找到bug和崩潰原因,而程式崩潰是Apple在審查時拒絕app上架的首要原因。

單元測試不是萬能的,但Apple把它作為開發套件的一部分,不僅讓你創作的APP更穩定,而且提供了一致、有趣的使用者體驗,這些都是讓使用者給你五星評價的源泉!iOS7提供了一個升級的單元測試架構,讓你在Xcode中運行單元測試更為容易。當你完成這一章節,你將學會如何給現有app添加測試——並有可能培養出對編寫測試的熱愛!

/*

本文翻譯自《iOS7 by Tutorials》一書的第十一章“Unit Testing in Xcode 5”,想體會原文精髓的朋友請到Raywenderlich商店支援正版。

——————(部落格園、新浪微博)葛布林大帝

*/

 

目錄:

一、單元測試基礎

二、開始項目

三、下一步何去何從?

四、挑戰

附錄:XCTest斷言參考

 

一、單元測試基礎

在過去,Xcode引入了一個叫做OCUnit的開源單元測試架構。而在Xcode 5裡面,Apple發布了他們自己的的單元測試架構,叫做XCTest。

如果你已經熟悉OCUnit,別擔心,XCTest是一個建立在OCUnit之上並且十份相似的API。從OCUnit過渡到XCTest非常簡單,要做的僅僅是把STFail替換為XCTFail、STAssert替換為XCTAssert等等諸如此類。如果你已經熟悉這些基礎,可以直接跳到下一節。

 

 

1.高層次概述

 

 單元測試有四個層級。從上到下,它們分別是:

 

  • 測試套件(Test suite)
    • 這是項目裡測試的全部集合。在Xcode裡,測試套件被設定為一個單獨的build target。
  • 測試案例類(Test case classes)
    • 正如你可能所期待的,在一個物件導向體系中,測試被整合到類裡。在你的app裡,每個測試類別通常對應一個單獨類。例如,DeveloperTests類應該對應Developer類的測試。
  • 測試案例方法(Test case methods)
    • 每個測試類別包含多種方法,用來測試類別的各種功能。就像一個方法或函數應該既精簡又實用,每個測試案例應該測試一個特定的結果——並且完全測試。
  • 斷言(Assertions)
    • 斷言檢查對應預期結果的具體條件。如果條件不符合預期結果, Xcode會報錯指出宣告失敗。例如,可以斷言你的Developer 類響應“writeKillerApp: message”;如果它沒有,宣告失敗,Xcode報錯。

 

 

理論很美好,但有時舉例會更容易闡述事物。用Empty Application 模板建立一個新項目,命名為EmptyApp。 Xcode模板會自動包含一個叫做EmptyAppTests的test target,添加到EmptyApp的 app target裡,如:

 

注意測試案例類包含了一個沒有關聯標頭檔的.m檔案,開啟EmptyAppTests.m看看第一個測試案例的原始碼。

測試方法必須以單詞test開始,以便test runner能找到它們。在你的樣本項目裡,測試類別包含了一個測試方法,叫做testExample

 

setUptearDown方法就像守護在測試案例周圍的衛兵一樣。

把所有對象的程式設定代碼或重複性代碼放到setUp裡,使測試案例方法保持清爽、高效。

類似的,關閉檔案控制代碼或取消掛起網路請求等清理活動的方法應該放到tearDown裡。

 

Test runner 會依次調用setUp、testExample和tearDown方法。如果你申明了第二個測試方法testSecondExample,Test runner會依次調用setUp、testSecondExample,最後是tearDown方法。如果你有多個測試方法,setUp和tearDown會在一個測試環節調用多次——每經過一個測試案例方法調用一次!

 

這個故事的寓意是不要放任何處理太慢或處理頻繁的東西到setUp或tearDown方法裡——這會讓你運行測試套件時面臨漫長的等待

 

 

 

2.建立你的第一個測試

 

 

testExample方法只有一個叫做XCTFail的語句,正如它名字裡暗示的:總是會失敗。這個語句不是非常有用,你可以寫一個比它更好的!刪除testExample方法,並添加如下方法:

- (void)test_addition_twoPlusTwo_isFour 
{  XCTAssert(2 + 2 == 4, @"2 + 2 should be 4 but %d was returned instead", 2+2);}

測試案例的一個常用命名標準是:unitOfWork_stateUnderTest_expectedBehavior (工作單元_測試狀態_預期行為)。

在這個例子裡,被測試的工作單元是加法,測試狀態是2 + 2,預期行為是結果為4。

所有XCTest斷言都有首碼XCT。XCTAssert是可用於單元測試的簡單斷言,第一個參數是預評估為ture的運算式,當宣告失敗時,其後NSLog風格的參數會顯示一條訊息。

 

確保項目的當前target為iPhone模擬器,通過視窗頂部目錄的Product -> Test(Command-U)來運行測試,模擬器會啟動並執行測試套件。如果通知處於啟用狀態,你會看到下列確認訊息:

 

 

為了證實第一個單元測試成功,切換到Test Navigator,箭頭指出了它:

哈哈!翠綠色的小勾旁邊顯示出了你的單元測試。

你還可以看到邊框空白處菱形表徵圖旁的代碼,如下所示:

這些表徵圖展示關聯測試代碼的狀態:

@implementation旁的綠色小勾表示這個類測試通過,test_addition_twoPlusTwo_isFour旁的綠色小勾表示這個方法測試通過。

同時,這些表徵圖也是按鈕:

點擊@implementation旁的表徵圖將會運行這個類的所有測試,點擊其他測試方法旁的表徵圖則會運行該測試方法,試一試吧!

 

現在你已經對測試的概念和執行有了初步瞭解,是時候開始本章的樣本項目了——測試開始!

 

二、開始項目

本章的剩餘部分你將使用一個名為Reversi的黑白棋遊戲項目,規則:兩個玩家,分別代表白方和黑方,輪流在8x8棋盤上落子。通過包圍對方棋子來吃掉它,遊戲結束時棋子最多的為勝者。

如何建立這個遊戲,請看:http://www.raywenderlich.com/29228/how-to-develop-an-ipad-board-game-app-part-12

下載本文頁尾提供的樣本項目並運行,點擊螢幕下方的Vs Computer按鈕與電腦進行對戰,感受一下這個遊戲的介面和玩法。

你獲勝了嗎?或者被AI對手爆出翔?不管怎樣,你的工作不是整日玩遊戲——是時候添加一些有用的測試到項目裡了。

 

1.添加測試的支援

第一個需要單元測試的是GameBoard類。這個類囊括了8x8棋盤的基本邏輯,64個儲存格中的每個都有一個狀態——空、黑棋或白棋——並且GameBoard執行個體讓你能擷取並設定每一個方塊的狀態。 

開啟GameBoard.h看一下裡面的方法,在開始為現有代碼編寫測試之前,弄清楚各方法的作用和實現是一個好主意。

GameBoard.h,你會看到下列兩個方法:

// gets the state of the cell at the given location// raises an NSRangeException if the column or row are out of bounds- (BoardCellState) cellStateAtColumn:(NSInteger)column andRow:(NSInteger)row;// sets the state of the cell at the given location// raises an NSRangeException if the column or row are out of bounds- (void) setCellState:(BoardCellState)state forColumn:(NSInteger)column andRow:(NSInteger)row;

cellStateAtColumn:andRow: 和 setCellState:forColumn:andRow: 由你非常熟悉的getter/setter模式裡發展出來,你的第一個測試是執行如下動作:

  • 初始化一個GameBoard執行個體
  • 設定cell狀態
  • 擷取cell狀態
  • 從指定的cell裡擷取cell狀態

 

第一步是建立一個GameBoard測試類別,右擊ReversiGameTests分組,選擇 iOS\Cocoa Touch\Objective-C test case class 建立一個名為GameBoardTests的測試類別,繼承自XCTestCase。

確保你的新測試案例添加到ReversiGameTests target,如(這個步驟非常重要,如果沒添加到正確的target裡,你的測試不會運行):

 

開啟 GameBoardTests.m 並且刪除 testExample 方法,你不需要它。

然後在 GameBoardTests.m 頂部匯入標頭檔(這僅僅是讓你的測試類別能夠訪問GameBoard類):GameBoard.h 

#import "GameBoard.h"

 

你需要為你的所有測試提供一個GameBoard 執行個體,建立一個執行個體變數會比在每個測試裡申明一個清爽得多。

GameBoardTests.m 裡更新@interface 如下:

@interface GameBoardTests : XCTestCase 
{
  GameBoard *_board;}

現在你有了_board執行個體變數,可以開始測試了。

 

setUp 方法是第一次初始化_board的好地方,修改setUp如下:

- (void)setUp{  [super setUp];  _board = [[GameBoard alloc] init]; }

現在這個類的所有測試案例方法都能夠訪問初始化後的_board執行個體變數了。

 

2.第一個測試

這是你需要為首個測試案例添加的所有步驟,添加以下方法到GameBoardTests.m

- (void)test_setAndGetCellState_setValidCell_cellStateChanged {  [_board setCellState:BoardCellStateWhitePiece forColumn:4 andRow:5];
  BoardCellState retrievedState = [_board cellStateAtColumn:4 andRow:5];
  

  XCTAssertEqual(BoardCellStateWhitePiece,           retrievedState,           @"The cell should be white!");}

上面的代碼在(4,5)儲存格裡設定了一個白棋,並且立刻檢索了相同儲存格的狀態。XCTAssertEqual 斷言檢查它們是否相等,如果不相等,你會看到一個異常資訊,然後你將得知有一些東西需要檢查。

上面代碼的方法名遵循我之前提到的格式,通過這個方法名,你可以很容易看出它通過設定正確的儲存格位置來測試setter和getter方法,並期待儲存格狀態的改變。

 

如果你的測試工作是有計劃的,確保iPhone和iPad模擬器都測試,然後運行測試(Command-U)。

切換到Test Navigator,你會看到一個綠色小勾表示測試通過,如:

這看起來只是一個簡單的測試,但是它在調試錯誤裡提供了巨大的價值。

 

在內部, GAMEBOARD類使用一個簡單的二維數組來跟蹤8X8棋盤。但如果你曾經改變了代表向量或矩陣的數組,本次測試將作為迴歸測試,確保interface 的基礎仍在工作。

作為一個附帶的好處,為現有的類編寫測試可以大大有助於理解代碼是如何工作的。分析類的方法可以協助你辨別其功能,並為你編寫測試提供便利。

 

3.測試異常

按照設計的功能測試代碼有助於確保其正確性,但也使得你的app“早早失敗或高調失敗”——那些異常遊戲狀態或無效條件被調試器很快抓住。

GameBoard.h裡cellStateAtColumn:andRow: 和 setCellState:forColumn:andRow: 方法的注釋表明,如果行或列超出棋盤邊框,它們會彈出錯誤。看起來你已經找到更多的測試條件。

添加下列兩個方法:

- (void)test_setCellState_withInvalidCoords_exceptionThrown {XCTAssertThrowsSpecificNamed([_board setCellState:BoardCellStateBlackPieceforColumn:10andRow:7], NSException,NSRangeException,@"Out-of-bounds board set should raise an exception");}- (void)test_getCellState_withInvalidCoords_exceptionThrown {XCTAssertThrowsSpecificNamed([_board cellStateAtColumn:7 andRow:-10],NSException,NSRangeException,@"Out-of-bounds board access should raise an exception");}

上面的代碼裡,test_setCellState_withInvalidCoords_exceptionThrown: 試圖設定超出範圍的儲存格(10,7),同時test_setCellState_withInvalidCoords_exceptionThrown: 試圖擷取超出範圍的儲存格(7,-10)。再次的,方法名已指出在正測試不正確的座標,報出異常正在意料之中。

 

XCTAssertThrowsSpecificNamed 採用以下四點作為參數:

  • 應該報出異常的運算式
  • 排除的類
  • 排除的名稱
  • 測試失敗時顯示的訊息

點擊Command-U運行測試,你應該看到以下結果

這是什嗎?你希望用出色的代碼通過測試,但是兩個錯誤標記在Issue Navigator上。測試失敗資訊也會顯示在代碼上,如:

 

所有的測試失敗訊息為:

[GameBoardTests test_getCellState_withInvalidCoords_exceptionThrown] failed: (([_board cellStateAtColumn:7 andRow:-10]) throws <NSException, "NSRangeException">) failed: throwing <NSException, "NSGenericException", "row or column out of bounds"> - Out-of- bounds board access should raise an exception

如果你分解上面的訊息,你會看到你希望的行為是(throws <NSException, "NSRangeException">) ,而實際發生的是(throwing <NSException, "NSGenericException">) 。

在這個例子裡,你期待的是NSRangeException ,但接收到的卻是NSGenericException 。

看起來你已經做了一些研究!

 

樣本項目地址:http://pan.baidu.com/s/1o6x6zxg

 

 

 

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.