[SwiftUI] [UnitTest] [Xcode] Xcode を使ったテストの始め方

TDD

Xcode を使ったアプリケーション開発で、UnitTest と UITest を行うための設定を改めて説明します。

環境&対象

以下の環境で動作確認を行なっています。

  • macOS Catalina 10.15.7
  • Xcode 12.2RC

プロジェクトにテストを設定する方法

Xcode のプロジェクトにテストを設定する方法を説明します。

「新規プロジェクト作成時に設定する方法」と「既存プロジェクトに追加で設定する方法」の2つがあります。

新規プロジェクトの時に設定する方法

Xcode で新規プロジェクトを選択すると、以下のようなステップでプロジェクトを作成します。

  1. "Choose a template for your new project" で、プラットフォーム / プロジェクトのタイプを選択
  2. "Choose options for your new project" で、Product Name や Interface 等を選択
  3. プロジェクト保存場所の選択と Git リポジトリを作るかどうかの選択

手順2の箇所で、"include Tests" を選択することで、テスト用のターゲットとテスト用のコードが追加されます。

この操作では、UITest と UnitTest の2つのテストが追加されます。

Project setting for test

「Project setting for test」

既存プロジェクトに追加で設定する方法(新規テストターゲットを追加する)

以下の手順で、追加していきます。

  1. プロジェクトナビゲータ(⌘1)で、プロジェクトを選択
  2. "Choose a template for your new target" で、"UI Testing Bundle" もしくは、"Unit Testing Bundle" を選択
  3. "Choose options for your new target" で、新しいテストターゲットの詳細を設定

以下は、手順2で、UI Testing Bundle を選んでいる画面

Select UI Testing Bundle

「Select UI Testing Bundle」
UITest と UnitTest の両方を追加したい時は、手順2でそれぞれを選んで、2つのターゲットを追加する必要があります。

テンプレートで生成されているコードの意味

UnitTest

プロジェクト名 Empty というプロジェクトでは、EmptyTests というターゲット名で UnitTest のコードが以下のように生成されます。

UnitTest code

//
//  EmptyTests.swift
//
//  Created by : Tomoaki Yagishita on 2020/11/11
//  © 2020  SmallDeskSoftware
//

// (1)
import XCTest

class EmptyTests: XCTestCase {
    // (2)
    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    // (3)
    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }
    // (4)
    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }
    // (5)
    func testPerformanceExample() throws {
        // This is an example of a performance test case.
        measure {
            // Put the code you want to measure the time of here.
        }
    }

}
コード解説
  1. 対象のアプリ/モジュールの内部クラス等をテストするためには、@testable import <modlue name> を追加する必要があります
  2. テスト用のセットアップコードを記載します。各テスト関数実行前に実行されます。
  3. テスト用の終了コードを記載します。書くテスト関数実行後に実行されます。
  4. テスト関数の例です。"test" という名称で開始することが必要です。ここでのコードは、空の関数になっています
  5. パフォーマンスをテストするためのコード例になっています。measure で指定された closure が測定対象となります。

UITest

プロジェクト名 Empty というプロジェクトでは、EmptyUITests というターゲット名で UITest のコードが以下のように生成されます。

UITest code

//
//  EmptyUITests.swift
//
//  Created by : Tomoaki Yagishita on 2020/11/11
//  © 2020  SmallDeskSoftware
//

// (1)
import XCTest

class EmptyUITests: XCTestCase {
    // (2)
    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.

        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }
    // (3)
    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }
    // (4)
    func testExample() throws {
        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        app.launch()

        // Use recording to get started writing UI tests.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }
    // (5)
    func testLaunchPerformance() throws {
        if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
            // This measures how long it takes to launch your application.
            measure(metrics: [XCTApplicationLaunchMetric()]) {
                XCUIApplication().launch()
            }
        }
    }
}
コード解説
  1. UITest は、アプリ/モジュールを外部からテストするため、@testable import <modlue name> を追加する必要は、ありません。
  2. テスト用のセットアップコードを記載します。各テスト関数実行前に実行されます。
  3. テスト用の終了コードを記載します。書くテスト関数実行後に実行されます。
  4. テスト関数の例です。"test" という名称で開始することが必要です。ここでのコードは、空の関数になっています
  5. パフォーマンスをテストするためのコード例になっています。measure で指定された closure が測定対象となります。

テストを実行する方法/確認する方法

テストを実行する方法を説明します。

プロジェクトに含まれる全てのテストを実行する方法

Xcode メニューの [Product]-[Test] (⌘U) を選択すると、プロジェクトに含まれる全てのテストが実行されます。

Test 実行メニュー

「Test 実行メニュー」

テストクラスに含まれる全てのテストを実行する方法

テストクラスの左側にマウスをホバーすると表示される実行可能シンボルをクリックすることで、該当クラスに含まれるテストを実行することができます。

クラスに含まれるテストを実行

「クラスに含まれるテストを実行」

1つのテスト関数を実行する方法

テストメソッドの左側にマウスをホバーすると表示される実行可能シンボルをクリックすることで、該当テスト関数を実行することができます。

特定のテストを実行

「特定のテストを実行」

テストの結果を確認する方法

Tests ナビゲータ(⌘6) でテスト結果を確認することができます。

Test result

「Test result」
Tests ナビゲータから、テストを実行させることもできます。テストターゲット、テストクラス、テストメソッド それぞれの右側にマウスホバーで表示される 実行可能シンボルをクリックすると テストが開始されます。

新しいテストを追加する方法

最初に作成されたファイル以外でも、XCTestCase を継承したクラスで、test で始まるメソッドであればテスト対象となります。

既存ファイルに、新規テスト関数追加

test で始まる新しいメソッドを定義すると、自動的にテスト対象となります。

UnitTest / UITest いずれの場合も、既存の setUpWithError/tearDownWithError は、テスト前後に実行されますので、意識する必要があります。

以下のようなメソッド名をつけると、なんのテストをしているかわかりやすくなるので、おすすめです。
テスト内部は、Arrange: "事前準備” -> Act: "テスト対象操作実行" -> Assert: "テスト結果確認" という順番を守って記述するのがおすすめです。

example code

    func test_<#methodName#>_<#withCertainState#>_<#shouldDoSomething#>() {
      <#Arrange#><#,Act#><#,Assert#>
    }

新規ファイル追加

プロジェクトへ新規ファイルを追加します。特に、専用メニュー等はありません。

新規ファイルを作成する時に、"UI Test Cacse Class" と "Unit Test Case Class" というテンプレートが選択できますが、これらのテンプレートは、Objective-C 用のファイルを作成しますので、Swift で記述するのであれば、"Swift File" を選択して新規ファイルを追加します。

以下は、自分向けに作成した新規 UnitTest 向けのテンプレート と UITest 向けのテンプレートです。

Xcode は、自動で作ってくれませんので、自分でこのようなテンプレートを作っておくと便利です。

なお、テンプレートは、パフォーマンステスト向けのコードを含んでいません。

UnitTest code

//
//  #MyOwn#Tests.swift
//
//  Created by : Tomoaki Yagishita on 2020/11/11
//  © 2020  SmallDeskSoftware
//

import XCTest
@testable import #MyModule#

class #MyOwn#Tests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // This is an example of a functional test case.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }
}
UITest code

//
//  #MyOwn#UITests.swift
//
//  Created by : Tomoaki Yagishita on 2020/11/11
//  © 2020  SmallDeskSoftware
//

import XCTest

class #MyOwn#UITests: XCTestCase {

    override func setUpWithError() throws {
        // Put setup code here. This method is called before the invocation of each test method in the class.

        // In UI tests it is usually best to stop immediately when a failure occurs.
        continueAfterFailure = false

        // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
    }

    override func tearDownWithError() throws {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
    }

    func testExample() throws {
        // UI tests must launch the application that they test.
        let app = XCUIApplication()
        app.launch()

        // Use recording to get started writing UI tests.
        // Use XCTAssert and related functions to verify your tests produce the correct results.
    }
}

まとめ: Xcode を使ったテストの始め方

Xcode を使ったテストの始め方
  • 新規プロジェクトでは、"includes Test" にチェックを入れる
  • 既存プロジェクトには、新規ターゲット追加でテストを追加する
  • UITest と UnitTest がある

説明は以上です。Happy Coding !

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です