SwiftのUnit Testを大量に書く機会があったので、Xcode10でのテスト環境の構築についてメモ。
Xcodeでのテスト
Xcode には標準でUnit Test と UI Test が備わっている。今回は、Unit Testについてまとめる。プロジェクトにTestコードが追加されていない場合は、File -> New -> Target -> iOS Unit Testing Bundle からテストBundleを追加する。“アプリケーション名+Tests” ディレクトリが新しく追加されるので、その中の“アプリケーション名+Tests.Swift”にテストコードを記述して行く。
ユニットテストの書き方
初期状態では、このようなテストコードが記述されてる。
import XCTest @testable import project_name class Tests: XCTestCase { override func setUp() { } override func tearDown() { } func testExample1() { } func testExample2() { } func testPerformanceExample() { self.measure { } } }
-setUp()
は各テストケース(testExample1
、testExample2
、testPerformanceExample
)の前に呼ばれるメソッドで、パラメータの初期化等に使う。逆に-tearDown()
は各テストケース終了後に呼ばれる。つまりメソッドが呼ばれる順番は以下のようになる。
-setUp()
-testExample1()
-tearDown()
-setUp()
-testExample2()
-tearDown()
-setUp()
-testPerformanceExample()
-tearDown()
各テストケースは、XCTestCaseのサブクラスの中で、メソッド名の頭を「test」にすると、テストケースとして自動で認識してくれる。テストの実行は、Navigator Barから、Test Navigatorを開く。各メソッドまたはBundleの横に表示される実行ボタンを押せばテストが実行される。
https://help.apple.com/xcode/mac/10.1/index.html?localePath=en.lproj#/dev5c935e39c
同期テスト
関連機能が多いので全ては割愛するが、基本的には、
-XCTAssertTrue(実際の値)
- 実際の値がTrueの場合は、テストをパス
-XCTAssertEqual(実際の値, 予想される値)
- 実際の値と予想される値が同じ場合は、テストをパス
のように、テストケースを追加して行く。
https://developer.apple.com/documentation/xctest/defining_test_cases_and_test_methods
https://qiita.com/sunstripe/items/8102e79b4b3c71ab3508
非同期テスト
ネットワーク系の通信テストや、通知系のテストの場合は、非同期でのユニットテストが必要になる。
-expectation(description)
メソッドを使ってXCTestExpectation
インスタンスを生成-wait(for:timeout:)
を使って、上記のXCTestExpectationの-fulfill()
が呼ばれるまで待つ。timeout 引数に設定した時間(秒)までに-fulfill()
メソッドが呼ばれない場合は、エラーが呼ばれる。- 目的のイベントが発生したら、XCTestExpectationの
-fulfill()
メソッドをコールして、待機状態から抜け出す。
下記は、Timerを使った非同期処理のテストコード
let expect = expectation(description: "async test") let timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in expect.fuill() } wait(for: [expect], timeout: 3) timer.invalidate()
-fulfill()
が二回呼ばれると、API Violation Error でテストが失敗するため、非同期処理が継続して繰り返し実行させる場合、テストが終わったらその処理は必ず終了させる。
パフォーマンス計測
-self.measure((Void)->Void)
を使えば、メソッドの処理時間を計測できる。measure
ブロック内での処理を10回繰り返して、各処理にかかった時間を表示してくれる。以下は0から100までを表示する処理時間の計測。
func testPerformanceExample() { self.measure { for i in 0..<100 { print(i) } } }
計測が終わると灰色のアイコンが表示させる。そのボタンを押すと、計測結果の詳細が表示させる。まだあまり試してないが、DBの読み書き処理をテストする予定。
Interval メソッドテスト
テスト対象のクラスをインポートする際に、@testableを付ければpublicメソッドで無くてもアクセス可能。ただし、privateメソッドとして定義されてるメソッドにはアクセスできないので注意。
@testable import className
https://qiita.com/motokiee/items/d9314b7e1e8f6cce7483
コードカバレッジ
Xcodeはテストコードのコード網羅率(Code coverage)の測定機能もサポートしている。Wikipediaによると、コード網羅率の主な計測手法は以下の5種類がある。
- 文網羅 (Function coverage) – ソースコードの各文がテストで実行されたかどうかで判断する。
- 分岐網羅 (Statement coverage) – 制御構造上の分岐でそれぞれの分岐方向がテストされたかどうかで判断する。
- 条件網羅 (Edge coverage) – 分岐条件の各項で真と偽の両方がテストされたかどうかで判断する。
- 経路網羅 (Branch coverage) – 対象コードの考えられる全ての経路についてテストで実行されたかどうかで判断する。
- 入口/出口網羅 (Condition coverage) – 存在する全ての関数呼び出しがテストで実行されたかどうかで判断する。
この分類で言うと、Function coverage で網羅率を算出してくれる。
Xcodeでは、Code Coverageはオフになっているので、メニューからProduct → Scheme → Edit Scheme → Test からCode Coverageにチェックを入れる。
この状態でテストを走らせると、コードカバレッジがパーセンテージで表示される。
https://help.apple.com/xcode/mac/10.1/index.html?localePath=en.lproj#/dev9e0e09978
実機とシュミレータの切り分け
加速度センサなど、シュミレータではテストを実行できない場合がある。その場合には、以下のソースコードを使ってテスト処理を分岐させる。アルゴリズムのテストの場合などは、ダミーデータを使って、予想通りの結果が返ってくるかをテストする。
#if targetEnvironment(simulator)
// Tests on a simulator
#else
// Tests on a real device
#endif
xcodebuild を使ったテストの自動化
xcodebuild を使って対象プロジェクトをコマンドラインからテストできる。オプションはman xcodebuild
で確認できる。
set -o pipefail && xcodebuild test -enableCodeCoverage YES -workspace [アプリ名].xcworkspace -scheme [アプリ名] -sdk iphonesimulator12.1 -destination OS=12.1,name="iPhone X" ONLY_ACTIVE_ARCH=NO CODE_SIGNING_REQUIRED=NO -allowProvisioningUpdates | XCPRETTY
Travis CI を使ったテストの自動化
上記で設定したxcodebuildのコマンドは、Travis CIでも同様に実行できる。Travis CIの設定は、過去にまとめたので、そちらを参照してください。リンクはこちら。
現時点でのテスト環境なので、継続的にアップデートして行こうと思います。