対象読者
・swiftを始めたばかりな方
・Testをまともに書いたことのない方
・Testって何?な方
・swiftすきな方
Testの大切さ
まず、動作確認(Test)はとても大切です。
作ったアプリがきちんと動くか、実行して自分で触って予定通りの動作を確認する。
これはとても大切でリリースした後にバグ祭りなんて自体を少なからず避けられます。
Testは何のためにするのか?
Testをするのは
・動作の確認の為
・動作を保証する為
・変更に耐えられる様にする為
これらが挙げられます。
その為、これが保証されれば、「自分で実行させて触って動作を確認する」というTestでもいいのです。
手動Testは現実的か?
実際、開発が進むたびに手動でテストするのはとても気が遠くなります。
アップデートを施せば、手動テストで通っていた場所も当然の様にバグが顔を出します。(コンニチハ!!!)
Testを書くのはめんどくさい。
Test書いたことない頃は「Test書くのめんどくせえ」と筆者は思っていました。
というか正直Testを書いている今でもめんどくさいです。
ただ、開発スピードを考えても自動でテストを行うほうが速いし、長期的にはかなり楽になります。
簡単なTestを書いてみよう。
プロジェクトを作る際にXCTestとXCUITestを追加しておきます。
(後から追加できます。)
単体テスト
単体テスト(同期)
テスト対象は以下の様に、よくあるただ足し算をする関数。
import Foundation
final class SyncModel {
func add(_ x: Int, _ y: Int) -> Int {
return x + y
}
}
そして以下がそのテストコード。
func testSyncAdd() throws {
let x = 10
let y = 5
let ans = syncModel.add(x, y)
XCTAssertEqual(ans, 15)
}
XCTAssertEqualは二つの引数を比較して一致しなければエラーを投げます。
単体テスト(非同期)
次に非同期のテスト。
closureに計算結果を渡すものです。
import Foundation
final class AsyncModel {
func add(_ x: Int, _ y: Int, completion: @escaping (Int) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
completion(x + y)
}
}
}
そして以下が対象のテストコード
func testAsyncAdd() throws {
let exp = expectation(description: "add function")
let x = 10
let y = 5
asyncModel.add(x, y) { ans in
XCTAssertEqual(ans, 15)
exp.fulfill()
}
wait(for: [exp], timeout: 5)
}
expectationを使って非同期処理のテストを行ってます。
wait関数を記述しなければ非同期処理を待たずにこのテスト関数は終了し、正しいテストはできません。
ではwaitは何を待っているのでしょうか?
それは引数forで指定したexpがfulfillを投げるのを待っています。
非同期処理が終わったタイミングでexpがfulfillを読んでいるのが確認できますね。
これでwait以下が実行されるので段階的に実行することができるというわけですね。
UITest
例として遷移のテストを行います。
コードはここに書くには少し鬱陶しいので簡単に説明します。
「ボタンを押したら別のViewControllerに遷移するアプリ」です!!!
これに対するテストは
func testPush() throws {
let app = XCUIApplication()
app.launch()
let pushButton = app.buttons["pushButton"]
XCTAssert(pushButton.exists)
pushButton.tap()
let backButton = app.buttons["Back"]
XCTAssert(backButton.waitForExistence(timeout: 5))
backButton.tap()
let pushButton2 = app.buttons["pushButton"]
XCTAssert(pushButton2.waitForExistence(timeout: 5))
}
まず、app.launchでアプリを起動します。
次に遷移するためのボタンを取得します。
これは、UIの宣言側で
button.accesibilityIdentifier = "pushButton"
などと設定することでTest側でUIを特定できる様にします。
Test側はapp.buttons["pushButton"]
で取得できるのですからよくできています。(ウンウン)
プッシュされた先のViewControllerでは設定していなければ左上のナビゲーションアイテムとしてBackボタンが設定されているはずです。
そのため、
let backButton = app.buttons["Back"]
XCTAssert(backButton.waitForExistence(timeout: 5))
この様にBackボタンを取得、存在を確認することでテストを行います。
結合テスト
単体テストでやった単純な足し算関数とUIテストを組み合わせてテストします。
テスト対象は「足し算の入力を設定する2つのテキストフィールドとボタンを持ち、遷移先には足し算の結果を出力するラベルを持つアプリ」です!!!
これに対する[テストコード]( func testAdd() throws {)は以下の様になります。
func testAdd() throws {
let app = XCUIApplication()
app.launch()
let xField = app.textFields["xField"]
let yField = app.textFields["yField"]
XCTAssert(xField.exists)
XCTAssert(yField.exists)
let pushButton = app.buttons["pushButton"]
XCTAssert(pushButton.exists)
xField.tap()
xField.typeText("10")
yField.tap()
yField.typeText("5")
pushButton.tap()
let backButton = app.buttons["Back"]
XCTAssert(backButton.waitForExistence(timeout: 5))
let ansLabel = app.staticTexts["ansLabel"]
XCTAssert(ansLabel.exists)
XCTAssertEqual(Int(ansLabel.label), 15)
}
例によってテキストフィールドはaccesibilityIdentifierにxField, yFieldを指定し、取得しています。
取得したUIにtapでフォーカス、typeTextで文字を入力してもらう動作をしていますね。
xField.tap()
xField.typeText("10")
続いて一つ前のUITestでやっていた様に遷移します。
遷移はBackボタンが取得できているかで完了確認を簡易的に行っています。
let backButton = app.buttons["Back"]
XCTAssert(backButton.waitForExistence(timeout: 5))
textFieldsやbuttonsなどのUIはそのままの名前だったので迷わず取得できますが、
ラベルはstaticTextsから取得します。紛らわしいですね!
ラベルのテキストはlabelプロパティで取得します。
値がしっかり反映されているかを確認してテストを終えます。
XCTAssertEqual(Int(ansLabel.label), 15)
如何だったでしょうか?
テストはとても大事です。(「「「2回目」」」)
ただ、今までテストを書いたことがない人がいきなり完璧なテストを網羅的に書くのも、現実的ではありません。
まずは一つ、簡単なテストを書いてみては如何でしょうか?