【Swift/Xcode】UI Test(User Interfaceテスト)の作成方法
この記事からわかること
- SwiftのXCTestフレームワークとは?
- XcodeでUI Test(User Interfaceテスト)を作成する方法
- 「UI Test(User Interface)テスト」と「Unit Test(単体テスト)」の違い
- テストコードの生成方法
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
iOSアプリ開発においてXCTestフレームワークを使用したUIテストの作成方法をまとめていきます。
UI Test(User Interfaceテスト)とは?
UI Test(User Interfaceテスト)はiOSアプリのユーザーインターフェース(UI)の機能と外観を検証するためのテスト方法です。実際のユーザーが行う操作をシミュレートしながらアプリケーションの正常な動作を確認することが目的になります。
全てのインタフェースをチェックしていては埒が開かないので、主要な機能に関わる部分を重点的にチェックしていきます。機能的なところ以外にもレスポンシブ性やレイアウト、コンテンツの表示速度などもテスト対象になります。
例えば以下のようなテストを行います。
- ボタンタップ時の画面遷移が正しいか
- フォームに入力された情報を正しく操作できているか
- レイアウトが崩れていないか
- APIで取得したデータを正しく表示できているか
XCTestでのUI Test
XcodeではXCTestフレームワークを使用して機能的な部分のUI Testを簡単に実装できるようになっています。
プロジェクトの作成時に「Include Tests」にチェックを入れていれば最初から組み込まれます。
プロジェクト作成後からテストを導入したい場合はXcodeの上部メニューから「File」 > 「New」 > 「Target...」をクリックし、Test内の「UI Testing Bundle」を選択して「NEXT」をクリックします。
ナビゲータエリアにテストターゲットが表示されていれば導入は成功です。(画像はUnit Testも一緒に組み込まれています)。
おすすめ記事:【Swift/Xcode】XCTestの使い方!Unit Test(単体テスト)の作成方法
UI Test(User Interfaceテスト)の作成方法
今回はUI Test(User Interfaceテスト)を実際に作成してみます。テストターゲットを追加すると「[プロジェクト名]UITests.swift」というファイルが作成され、中には以下のようなコードが記述されています。
import XCTest
final class TestUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
override func tearDownWithError() throws {
}
func testExample() throws {
let app = XCUIApplication()
app.launch()
}
func testLaunchPerformance() throws {
if #available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 7.0, *) {
measure(metrics: [XCTApplicationLaunchMetric()]) {
XCUIApplication().launch()
}
}
}
}
- setUpWithErrorメソッド:テスト開始前に実行
- tearDownWithErrorメソッド:テスト終了後に実行
- testExampleメソッド:テスト用メソッド
- testLaunchPerformanceメソッド:パフォーマンステスト用メソッド
- measure:時間を計測したいコードを記述
コードの中身はUnit Testとほぼ同じですがsetUpWithError
メソッドとtestExample
の中身が少し異なり、以下の2行が追加されています。
continueAfterFailure = false
let app = XCUIApplication()
app.launch()
continueAfterFailure = false
はテスト実行後に失敗(Failed)した際に中断するかどうを決めるフラグです。UIテストの場合は期待通りに動かなかった時にすぐにアプリを停止させるようにします。false
を渡すことで中断してくれます。
XCUIApplication().launch()
はアプリを起動させる処理です。
実装の手順
- テスト対象UIコードの準備
- UI操作の記録
- テストコードの作成
- テストの実行
テスト対象コードの準備
今回は単純なUIテストとして「User登録処理」が正しく動作するか確認してみます。期待する動作の流れは以下の通りです。
- TextFieldに名前を入力
- 「+」ボタンをタップ
- 入力した名前がリストに追加される
struct ContentView: View {
@State var name:String = ""
@State var users: [User] = [
User(name: "Johnny"),
User(name: "Michael")
]
var body: some View {
NavigationView {
VStack{
TextField("Name", text: $name).textFieldStyle(.roundedBorder)
List(users, id: \.id) { user in
Text(user.name)
.font(.headline)
}
}
.navigationBarTitle("User List")
.navigationBarItems(trailing:
Button(action: {
self.users.append(User(name: name))
}) {
Image(systemName: "plus")
}.accessibility(identifier: "addButton")
)
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct User: Identifiable {
let id = UUID()
var name: String = ""
}
UI操作の記録
どのようにUI Testを実行するかというとシミュレーターの操作を記録してくれる機能が用意されているのでそれを使用します。
「[プロジェクト名]UITests.swift」内のテスト用メソッドにカーソルを合わせ左下の赤丸をクリックするとシミュレーターが起動して記録を開始します。記録されるのはビューのタップやスクロール、入力などで、UI操作を行うたびにどんどんコードが自動で生成されていきます。
テストコードの作成
自動生成されたコードはそれほど精度が高くないので、不要な操作や期待と違う処理を修正して適切な流れになるように調整します。
func testExample() throws {
let app = XCUIApplication()
app.launch()
// 以下自動生成されたコードを元に編集
app.textFields["Name"].tap()
app.textFields["Name"].typeText("Bob")
app.navigationBars["User List"].buttons["addButton"].tap()
XCTAssertTrue(app.collectionViews.staticTexts["Bob"].exists)
}
上記のコードは以下のような動作を表しています。
func testExample() throws {
// アプリ操作用インスタンス生成
// アプリ起動
// 以下自動生成されたコードを元に編集
// Name:TextFieldをタップ
// Name:TextFieldにテキスト「Bob」を入力
// User List:NavigationBar > addButton:Buttonをタップ
// CollectionView内のセルにBobが存在するかチェック
}
テストの実行
これで準備が整ったのでテストを実行してみます。テストごとに「開始ボタン」が左側に表示されるのでクリック、もしくはCommand + U
で実行できます。
テストが期待通りに実行できると以下のように表示されます。
UI Test構築に使えるメソッド
自動で生成されたコードからある程度メソッドの使い道が見えてきます。よく使う操作メソッドをまとめておきます。
ビューを取得する
launch
メソッドでアプリを起動させたら、XCUIApplication
インスタンスから画面に表示されているButton
やTextField
などの全てのビューにアクセスできます。
画面に複数あるビューを識別するために識別子を指定します。識別子はButton
などのlabel
に設定している文字列です。
let text = app.staticTexts["Text"]
let textField = app.textFields["Name"]
let btn = app.buttons["ログイン"]
タップする
画面をタップするにはtap
メソッドを使用します。
btn.tap()
TextFieldに文字を入力する
TextField
に文字を入力したい場合は、typeText
メソッドを使用します。引数に入力したい文字列を渡します。またクリアーしたい場合はclearText
メソッドを使用します。
textField.typeText("Hello World")
textField.clearText()
ビューが存在するかどうか
例えば画面遷移をした際などに次の画面に正しく遷移したかを確かめるためにビューが存在するかどうかを取得することがあります。検証するためのXCTAssertTrue
などと一緒に使われることが多いです。
XCTAssertTrue(btn.exists)
ビューの識別子を指定する
labelに設定した文字列でビューを識別するのには同名のラベルの場合など、限界があります。その際はビュー側にaccessibilityidentifble
メソッドを使用して明示的に識別子を指定します。
Text("テキスト")
.accessibilityidentifble("MyText")
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。