Skip to the content.

1. テスト全体の話

1.1. なぜ

1.1.1. 自動テストのメリット

早いフィードバックを頻繁に受けることができる
コードの問題点にすぐに気づくことができる
手動テストで同じことを繰り返すとミスが起きる
当時の開発者がいなくても、仕様を把握できる / リファクタリングができる

1.1.2. 手動テストのための自動テスト

手動テストで担保するべき範囲がある

手動テストのための時間を確保するために自動テストが必要

1.2. テストピラミッド

The Forgotten Layer of the Test Automation Pyramid

種類 内容 テスト量
単体テスト 一つのクラス・構造体などに対して行うテスト
結合テスト 複数のモジュールを組み合わせて行うテスト
UI テスト UI を操作して行うテスト

ユーザーと同じ操作ができる UI テストを網羅しておけば、全ての機能の品質を担保できるように思えるが、コストがかかる。

逆ピラミッド型になっていると、テストは脆く崩れやすいと言われている。(アンチパターン)

1.3. でも時間だってない

テストのメリットはわかるが…

テストを捨てる勇気も必要

1.3.1. もうプロダクトコードがあるときには…

変更が少なく、重要な機能を優先してテストを実装する

2. テストの実行

2.1. 全てのテストを実行する

⌘ + U

2.2. 対象を絞って実行する

2.2.1. エディタを使う

◇ をクリック TestCase 単位や Test 単位でのテストの実行を行うことができます。

// TODO: add Image

2.2.2. テストナビゲータを使う

テストナビゲータを表示し、▷ をクリック

// TODO: add Image

2.3. ランダムに実行する

複数のテストが依存していないことを確認するためには、ランダムにテストを実行する方法があります。

// TODO: add Image | 手順 | | ——– | | | | |

3. 単体テスト

3.1. 目的

クラスや構造体などのある 1 つの部品(ユニット)に対するテスト

3.2. 注意点

ほとんどの場合、あるクラスは別のクラスに依存している。 例えば、Controller や View は Model に依存している。 Controller や View のテストをするためには、Model が必要になる。 しかし、依存しているクラスを含めてテストすると、テスト対象が不明確になる。

@startuml
Test対象 <.. Test対象が依存しているクラス
Testコード <-left- Test対象:使用
@enduml

3.2.1. モック(テストダブル)

この場合、モック(テストダブル)という手法を 使う。 Model をテスト用の偽物のコードに置き換えて、対象のクラスや構造体だけをテストする。

@startuml
 interface Test対象が依存 {}

 Test対象 <.. Test対象が依存

 Test対象 <|- Testコード:使用

 Test対象が依存 <|.d. Test対象が依存しているクラス

 Test対象が依存 <|.d. Testモック
@enduml

3.3. XCTAssertion 一覧

Assert 説明 コード例
XCTFail テストを失敗させる。
テストが正しく行われていないケースに入っている場合は失敗させる。
XCTFail
     
XCTAssertNil 結果が nil であることを期待 XCTAssertNil
XCTAssertNotNil 結果が nil でないことを期待 XCTAssertNotNil
     
XCTAssertEqual(expression1, expression2) expression1 と expression2 が一致することを期待
引数は Equatable に準拠する必要がある
expression1: 実際の値
expression2: 期待値
XCTAssertEqual
XCTAssertNotEqual(expression1, expression2) expression1 と expression2 が一致しないことを期待
引数は Equatable に準拠する必要がある
expression1: 実際の値
expression2: 期待値
XCTAssertNotEqual
XCTAssertTrue 結果が true であることを期待
XCTAssertEqual(expression1, true) でも書けるが、失敗時のログがよりわかりやすくなる
XCTAssertTrue
XCTAssertFalse 結果が false であることを期待
XCTAssertEqual(expression1, false) でも書けるが、失敗時のログがよりわかりやすくなる
XCTAssertFalse
     
XCTAssertGreaterThan(expression1, expression2) expression1 > expression2 を期待
引数は Comparable に準拠する必要がある
XCTAssertTrue( x > y ) でも書けるが、失敗時のログがよりわかりやすくなる
expression1: 実際の値
expression2: 期待値
XCTAssertGreaterThan
XCTAssertGreaterThanOrEqual expression1 ≧ expression2 を期待
引数は Comparable に準拠する必要がある
XCTAssertTrue( x ≧ y ) でも書けるが、失敗時のログがよりわかりやすくなる
expression1: 実際の値
expression2: 期待値
XCTAssertGreaterThanOrEqual
XCTAssertLessThan(expression1, expression2) expression1 < expression2 を期待
引数は Comparable に準拠する必要がある
XCTAssertTrue( x < y ) でも書けるが、失敗時のログがよりわかりやすくなる
expression1: 実際の値
expression2: 期待値
XCTAssertLessThan
XCTAssertLessThanOrEqual expression1 ≦ expression2 を期待
引数は Comparable に準拠する必要がある
XCTAssertTrue( x ≦ y ) でも書けるが、失敗時のログがよりわかりやすくなる
expression1: 実際の値
expression2: 期待値
XCTAssertLessThanOrEqual
     
XCTAssertThrowsError(expression, errorHandler) expression で例外が発生することをチェックする
errorHandler 内で例外の内容を検証できる
XCTAssertThrowsError
XCTAssertNoThrow 例外が発生しないことを期待 XCTAssertNoThrow
     

3.4. コードサンプル

3.4.1. XCTFail

func testMethod() {
    XCTFail()
}

3.4.2. XCTAssertNil

let notNumber = Int("Hello") // 数値に変換できずnil
XCTAssertNil(notNumber)

3.4.3. XCTAssertNotNil

let number = Int("42") // 数値に変換できInt
XCTAssertNotNil(number)

3.4.4. XCTAssertEqual

let string = "Hello"
XCTAssertEqual(string, "Hello") // "Hello"と等しい

3.4.5. XCTAssertNotEqual

let string = "Hello"
XCTAssertNotEqual(string, "Goodbye") // "Goodbye"と等しくない

3.4.6. Equatable に準拠する

// プロダクトコード
struct User {
    let name: String
    let age: Int
}
// テストコード
class UserTests: XCTestCase {
    func testInit() {
        let actual = User(name: "foo", age: 10)   // 実際の値
        let expected = User(name: "foo", age: 10) // 期待値
        XCTAssertEqual(actual, expected)
    }
}

// プロダクトコードを変更せずに、テストコードのみ Equatable に準拠することも可能
extension User: Equatable {
    static func ==(lhs: User, rhs: User) -> Bool {
        return lhs.name == rhs.name && lhs.age == rhs.age
    }
}

3.4.7. XCTAssertTrue

let string = "Hello"
XCTAssertTrue(string.hasPrefix("He")) // "He"から始まる

3.4.8. XCTAssertFalse

let string = "Hello"
XCTAssertFalse(string.isEmpty) // 空ではない

3.4.9. XCTAssertGreaterThan

// 20 > 10
XCTAssertGreaterThan(20, 10)

3.4.10. XCTAssertGreaterThanOrEqual

// 20 >= 10
XCTAssertGreaterThanOrEqual(20, 10)
XCTAssertGreaterThanOrEqual(20, 20) // 等しくてもOK

3.4.11. XCTAssertLessThan

// 10 < 20
XCTAssertLessThan(10, 20)

3.4.12. XCTAssertLessThanOrEqual

// 10 <= 20
XCTAssertLessThanOrEqual(10, 20)
XCTAssertLessThanOrEqual(10, 10) // 等しくてもOK

3.4.13. XCTAssertThrowsError

XCTAssertThrowsError(try throwError()) // throwError()がなんらかの例外をスローすることを期待
enum APIError: Error {
    case sampleError // なにかしらのエラー
    case anotherError  // 他のエラー
}

3.4.13.1. 例外の内容を検証する

// プロダクトコード
struct SampleModel {
    // 例外をスローする可能性がある API リクエスト
    static func fetch() throws {
        // API リクエストの処理...
        if httpStatusCode == 400 {
            throw APIError.anotherError
        }
        if httpStatusCode == 500 {
            throw APIError.sampleError
        }
    }
}
// テストコード
class SampleModelTests: XCTestCase {
    func testFetch() {
        XCTAssertThrowsError(try SampleModel.fetch()) { (error: Error) -> Void in
            // スローされた例外が APIError.sampleError であること
            XCTAssertEqual(error as? DownloadError, APIError.sampleError)
            // XCTAssertTrue(error! is APIError.sampleError)
        }
    }
}

3.4.14. XCTAssertNoThrow

XCTAssertNoThrow(try noThrowError()) // noThrowError()がなにも例外をスローしないことを期待

3.5. 非同期処理のテスト(XCTestExpectation)

非同期処理をテストする場合は、 XCTestExpectation を利用し、処理が完了するまで待機する。 処理の完了を待機していないと、テストが意図せず成功/失敗してしまう。

// プロダクトコード
struct SampleModel {
    // 例外をスローする可能性がある API リクエスト
    static func fetch(@escaping completion: (Result<User, Error>) -> Void) throws {
        // API リクエストの処理...
        // 結果を非同期に返却
        DispatchQueue.main.async {
            completion(.success(user))
        }
    }
}

struct User {
    let name: String
    let age: Int
}
// テストコード
class SampleModelTests: XCTestCase {
    func testFetch() {
        // 処理を待機させる
        let exp: XCTestExpectation = expectation(description: "wait for finish")

        SampleModel.fetch { (result: Result<User, Error>) in
            switch result {
            case .success(let data):
                XCTAssertEqual(data.agentId, 123)
            case .failure(let error):
                XCTFail("error: \(error)")
            }

            // expの待機を解除
            exp.fulfill()
        }

        // exp.fulfill()が実行されるまで、5秒間待機する
        wait(for: [exp], timeout: 5)
    }
}

4. 参考