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

4.測試失敗的調試ios

是時候追蹤以前測試失敗的問題了。打開GameBoard.m,找到cellStateAtColumn:andRow: 和 setCellState:forColumn:andRow: 方法,你會看到它們都調用了一個叫作checkBoundsForColumn:andRow: 的helper方法,用來檢測數組邊界。數組

頭文件 GameBoard.h 裏的方法註釋以下:app

// raises an NSRangeException if the column or row are out of bounds

然而,若是超出邊界,checkBoundsForColumn:andRow: 方法的實現拋出了一個NSGenericExpression 。一般的,你把頭文件裏的註釋當作一個公共API規範,但在這個例子裏代碼和規範並不匹配,你該如何作?框架

 一個可能性是更新註釋和相關的測試,以匹配當前的實現。在這個例子裏,規範裏的註釋看起來更有意義:一個邊界檢查應當遵循NSArray,並升起一個NSRangeException單元測試

 

在GameBoard.m裏,更新checkBoundsForColumn:andRow: 方法的實現以下:學習

- (void)checkBoundsForColumn:(NSInteger)column andRow:(NSInteger)row { if (column < 0 || column > 7 || row < 0 || row > 7) [NSException raise:NSRangeException format:@"row or column out of bounds"]; }

從新運行測試,這時全部測試應該可以經過了。測試

自從代碼不一樣步,註釋裏的規範老是有一點危險。然而,你的測試能夠爲註釋添加雙保險。自從你編寫測試代碼後,只要你常常運行測試,這些實現不匹配的風險會大大減少!spa

另外,你的測試提供了一個偉大的高層概覽代碼,特別是遵循建議的命名規範之後。當你從新進入好久沒碰過的代碼後,這會很是方便——正以下一小節的內容。調試

 

5.保證測試bugcode

一個崩潰報告剛剛進入你的app:你的一個測試人員報告你,當她運行遊戲後直接點擊屏幕(不點擊"2 Player"或者"vs computer"按鈕),程序就會崩潰。

你本身試一遍,就會在控制檯看到下列信息:

ReversiGame[1842:a0b] *** Terminating app due to uncaught exception 'NSRangeException', reason: 'row or column out of bounds'

崩潰看起來重複發生,是什麼拋出了一個NSRangeException ?call stack提供瞭如下信息:

2 CoreFoundation +[NSException raise:format:] + 139
3 ReversiGame -[GameBoard checkBoundsForColumn:andRow:] + 142 4 ReversiGame -[GameBoard cellStateAtColumn:andRow:] + 76
5 ReversiGame -[ReversiBoard flipOpponentCountersForColumn: andRow:withNavigationFunction:toState:] + 281
6 ReversiGame -[ReversiBoard makeMoveToColumn:andRow:] + 245 7 ReversiGame -[BoardSquare cellTapped:] + 192

從下往上讀:

第七、6行  tap觸發代碼用來處理玩家的移動

第5行 遊戲邏輯檢查是否有對手的棋子被包圍而且翻轉

第四、3行 代碼而後調用cellStateAtColumn:andRow: 和

checkBoundsForColumn:andRow:

第2行 底層框架報出一個越界異常

 

想了解更多調試崩潰的信息?看這裏

My App Crashed, Now What? http://www.raywenderlich.com/10209/my-app-crashed-now-what-part-1
Demystifying iOS Application Crash Logs http://www.raywenderlich.com/23704/demystifying-ios-application-crash-logs

這是一個測試這些崩潰條件的絕好機會。

你的新測試不止要修復這個問題,並且要做爲一個迴歸測試確保這個bug保持修復。沒有什麼比修復一個bug後,數月以後添加新功能或重構代碼時再遇到相同的bug更讓人不爽的了。

 

6.決定什麼須要測試

你知道你須要測試——但應該測試什麼?

ReversiBoard 是GameBoard類的通用實現,因此從這裏開始故障排除工做是有意義的。

 

使用iOS\Cocoa Touch\Objective-C test case class 模板建立一個新類,命名爲ReversiBoardTests, 繼承自XCtestCase。

在開始以前,刪除模板文件的testExample方法,而後在ReversiBoardsTests.m 裏導入頭文件:

#import "ReversiBoard.h"

 

ReversiBoardsTests.m 裏改變@interface 以下:

@interface ReversiBoardTests : XCTestCase { ReversiBoard *_reversiBoard; }

添加一個_reversiBoard 實例變量意味着你不用在每一個測試方法裏反覆實例化。

而後修改setUp方法以下:

- (void)setUp { [super setUp]; _reversiBoard = [[ReversiBoard alloc] init]; }

 

7.測試否認

在以前的編寫的測試中,異常的存在是預期的結果。這一次,異常並無在你的測試基礎上出現。

添加這些方法到ReversiBoardsTests.m 

- (void)test_makeMove_inPreGameState_nothingHappens { [_reversiBoard setToPreGameState]; XCTAssertNoThrowSpecificNamed( [_reversiBoard makeMoveToColumn:3 andRow:3], NSException, NSRangeException, @"Making a move in the pre-game state should do nothing"); }

上面的代碼中,測試設置遊戲前的狀態。也就是說,玩家做出對戰AI仍是對戰其餘玩家選擇以前的狀態。這個測試模擬了一進入遊戲就點擊棋盤的動做。

XCTAssertNoThrowSpecificNamed 斷言與XCTAssertThrowsSpecificNamed 恰好相反。若是指定的異常被拋出,上面的測試會失敗;若是指定的異常沒被拋出,測試會經過。

你尚未修復bug,因此如今運行代碼將會失敗——不過這是件好事,在修復bug以前編寫測試意味着你擁有重現bug的測試能力。

 

Command+U 運行測試,你會看到以下信息:

test failure: -[ReversiBoardTests test_makeMove_inPreGameState_nothingHappens] failed: (([_reversiBoard makeMoveToColumn:3 andRow:3]) does not throw <NSException, "NSRangeException">) failed

 

8.校訂代碼

打開 ReversiBoard.m 而後找到 makeMoveToColumn:andRow: 方法。

思考一下如何修正這個特定的bug。只有用戶選擇了遊戲模式以後才能夠移動,這是頗有意義的。這樣一想,遊戲前和遊戲後的遊戲邏輯沒有什麼不一樣。

幸運的是,這裏有一個屬性指定當前的遊戲狀態:gameState.

 

添加如下代碼到makeMoveToColumn:andRow: 方法的頂部:

if ([self gameState] != GameStateOn) return;

這個條件檢測了當前的遊戲狀態。若是狀態不是GameStateOn——說明遊戲不在運行中——方法當即終止。

運行app,測試一下在選擇遊戲模式以前點擊棋盤,是否崩潰?

 

最後,Command+U 運行測試,Test Navigator應該顯示綠色小勾,bug被碾碎了!

探索風格的測試只用包含明顯問題的代碼,然而回歸風格的測試則能夠爲常常修復某個問題提供了保障。

修復每一個bug不止是讓你的代碼更健康,同時讓你有更多時間思考你的單元測試。

 

 

3、下一步何去何從?

測試是開發生涯的一個巨大任務,這章咱們掌握了單元測試的基礎,下面是一些有益的概念:

  • 使用哪個斷言?在哪裏使用斷言?
  • 添加測試到一個現有app
  • 在程序說明裏使用測試
  • 探尋並修復bug
  • 確保已經修復過的bug再也不出現

 

Xcode中整合的XCTest讓創建測試套件很是容易,整個iOS領域的測試範圍是很是廣大的,更多測試概念:

  • Mock objects
    • 在測試裏模擬出足夠真實的對象
      • http://ocmock.org/
  • UI testing
    • 能夠模擬出用戶的輸入,好比touch或文本輸入。
  • Continuous integration (CI) systems
    • 將會自動運行單元測試,想了解更多關於CI的功能,閱讀本書1四、15章「Beginning and Intermediate Continuous Integration in Xcode 5  

 

在深度學習測試以前,這裏有幾個挑戰來讓你掌握本章的概念。

 

4、挑戰

GameBoard 類仍然還有一些方法沒被測試——你的任務是編寫測試,爲你的app提供一個完整的測試套件。

 

1.挑戰一:測試 clearBoard

clearBoard 清除棋盤上的全部棋子。自從已經測試getter和setter 方法後,你能夠假設這些方法無需再次測試。

celarBoard的測試用例有如下幾個步驟:

1)至少設置一個黑棋在棋盤上

2)至少設置一個白棋在棋盤上

3)調用clearBoard

4)檢查你如今放置白棋和黑棋的地方是空的(提示:狀態爲BoardCellStateEmpty)

記住測試用例遵循的命名格式:工做單元或方法名、測試什麼、預期的結果

 

2.挑戰二:測試scorekeeper

countCellsWithState: 記錄棋盤上特定狀態棋子的數量。這個方法計算出最後的分數,因此確保它正確工做是很是必要的! 

countCellsWithState: 的測試用例將執行如下動做:

1)設置一些黑棋或白棋在棋盤上

2)追蹤棋子增長的數量

3)比較你的數量與countCellsWithState:返回的數量

countCellsWithState:有一個狀態參數,因此它看起來像這樣

[_board countCellsWithState:BoardCellStateWhitePiece]

再次,肯定你的測試用例命名恰當

祝你測試成功!

 

附錄:XCTest斷言參考

下列全部斷言都使用(format...)做爲最後一個參數,這個NSlog風格的參數會在測試失敗時顯示消息。

XCTFail(format...)
無條件失敗;用來標記不該執行的代碼部分

XCTAssertNil(exp, format...)
XCTAssertNotNil(exp, format...)
表達式應爲nil或not nil;在OC對象中使用
 
XCTAssert(exp, format...)
XCTAssertTrue(exp, format...)
XCTAssertFalse(exp, format...)
表達式應爲true或false
 
XCTAssertEqualObjects(a1, a2, format...)
OC對象a1和a2應該相等;使用isEqual: 來保持相等
 
XCTAssertEqual(a1, a2, format...)
參數a1和a2應該相等;用來比較C數量、集合及結構體(如CGRect和CGPoint);使用NSValue來比較
 
XCTAssertEqualWithAccuracy(a1, a2, delta, format...)
參數a1與參數a2應該與給定的delta值相等;使用float和double類型,其中小數值可能不夠精確
 
XCTAssertThrows(exp, format...)
XCTAssertThrowsSpecific(exp, exception, format...)
XCTAssertThrowsSpecificNamed(exp,exception,exceptionName,format...)
表達式應該拋出一個異常信息;更詳細的版本讓你指出類名和異常名
 
XCTAssertNoThrow
XCTAssertNoThrowSpecific
XCTAssertNoThrowSpecificNamed
若是異常被拋出,這些斷言會失敗
相關文章
相關標籤/搜索