AAA 原則

那,我們來試試看單元測試這條途徑。

在 Xcode 裡頭建立專案的時候,Xcode 會幫我們的 App 同時建立一個單元測試的 Bundle—其實蘋果也鼓勵你寫單元測試—在這個 Bundle 中,會出現一個繼承自 XCTestCase 的 class,在裡頭撰寫任何用 test 開頭的 method,像 -testHit,都是一條test case。也就是,我們寫測試的時候,就是寫出一群用 test 為開頭的 method。

Xcode 從 5.1 版現在的測試框架叫做 XCTest,在這之前則是使用一套叫做 OCUnit 的測試框架(這邊的 OC 其實就是 Objective-C 的意思),除此之外, iOS 與 Mac OS X 平台上還有許多有名的測試框架,像是 GHUnitKiwi 等等。我們在這邊只先講解 XCUnit,如果你對單元測試有些心得,覺得 XCUnit 已經無法滿足你的需求,不妨也可以試試看其他的測試框架。

在撰寫測試的時候,基本原則是一次只測試一項 function 或 method,同時一個 test case 會包含所謂的 AAA 原則:Arrange、Act 與 Assert。

  • Arrange: 先設定我們在這次測試中,所預期的結果
  • Act: 就是我們想要測試的 function 或 method
  • Assert: 確認在 Action 發生後,確認在執行了想要測試 function 或 method 後,的確符合我們在 Arrange 階段設定的目標

舉個例子,我們預期一條長度為 6 、正在往左邊移動的蛇,在先往上走一格、再往右走一格、再往下走一格之後,這條蛇的頭一定會撞到自己的身體,如果我們的程式說蛇頭沒有撞到,就一定有 Bug。就可以拆解成:

  • Arrange: 頭應該會撞到身體
  • Action: 讓蛇執行往上右下移動的動作
  • Assert: 確認頭真的撞到身體了

這個 case 或許會像這樣:

- (void)testHit
{
    KKSnake *snake = [[KKSnake alloc]
      initWithWorldSize:KKMakeSnakeWorldSize(10, 10) length:6];
    [snake changeDirection:KKSnakeDirectionUp];[[snake move];
    [snake changeDirection:KKSnakeDirectionRight];[snake move];
    [snake changeDirection:KKSnakeDirectionDown];[snake move];
    XCTAssertEqual([snake isHeadHitBody], YES, @"must hit the body.");
}

如果我們想要測試「蛇的尾巴加長」這段程式是否正常,大概會這麼寫:原本這條蛇的長度為 2,尾巴位在 (6, 5),假如蛇的身體要加長兩格,我們預期蛇的長度變成 4,尾巴位在 (8, 5)。

- (void)testIncreaseLength
{
    ZBSnake *snake = [[ZBSnake alloc] initWithWorldSize:ZBMakeSnakeWorldSize(10, 10) length:2];
    XCTAssertEqual((int)[snake.points count], 2, @"Length must be 2 but %d", [snake.points count]);
    NSInteger x;
    NSInteger y;
    x = [snake.points[[snake.points count] - 1] snakePointValue].x;
    y = [snake.points[[snake.points count] - 1] snakePointValue].y;
    XCTAssertEqual(x, 6, @"must be 6");
    XCTAssertEqual(y, 5, @"must be 5");

    [snake increaseLength:2];
    XCTAssertEqual((int)[snake.points count], 4, @"Length must be 4 but %d", [snake.points count]);
    x = [snake.points[[snake.points count] - 1] snakePointValue].x;
    y = [snake.points[[snake.points count] - 1] snakePointValue].y;
    XCTAssertEqual(x, 8, @"must be 8");
    XCTAssertEqual(y, 5, @"must be 5");
}

results matching ""

    No results matching ""