XCTest UI Testとも仲良くなる
Swift Testingと仲良くなるでSwift Testingとは仲良くなれた気がしたので引き続きで、UI Testとも仲良くなりたいと思います。
公式サイト
UIテストの基本形
大きな流れは以下のとおりです。
-
XCUIApplication
によりアプリ起動する - 起動したアプリに対して、
XCUIElementQuery
を使用してUI要素(XCUIElement
)を見つける -
XCUIElement
に対して実施したい操作(クリックやタップなど)を実行する - 操作した結果に対してXCTestアサーション(
Swift Testing
)を利用して評価を実施する
つまり、以下の3つの関数を利用するのがベーシックな流れです。
テストコードの基本形
continueAfterFailure
はテスト失敗時に処理を止めるかの設定で、false
は止めずに評価を続ける設定になります。
import XCTest
final class CalculatorUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
override func tearDownWithError() throws {
}
@MainActor
func testExample() throws {
let app = XCUIApplication()
app.launch()
}
}
UIテストを実行してみる
実際に基本形でUIテストを実行してみましょう。まずは、UIテストの対象となるMainView
を以下の要領でコーディングしていきます。
ポイントは、UIElementごとにaccessibilityIdentifier(String)
で設定することで、どのUIコンポネントにアクセスするか を明確にしています。
UIテストの対象を整備する
以下のソースコードをテストしていきます。
UIは、テキストエリアと、0から9までのボタンが横並びする非常にシンプルなものです。
import SwiftUI
struct MainView: View {
@Binding var input: String;
var body: some View {
VStack{
TextField("入力してください", text: $input)
.disabled(true)
.foregroundStyle(.white)
+ .accessibilityIdentifier("calc_textfield")
.padding()
HStack {
ForEach((1...9), id: \.self) { num in
Button("\(num)", action: {
input.append(String(num))
})
+ // XCUITestでUIコンポネントに直接アクセスできるよう名前をつける
+ .accessibilityIdentifier("button_\(num)")
.frame(width: 45, height: 45)
}
}
}
.padding()
}
}
#Preview {
@Previewable @State var text: String = "";
return MainView(input: $text)
}
テストコードを書く
新しくtestButtonTap
関数をUIを評価する関数にします。
import XCTest
final class CalculatorUITests: XCTestCase {
override func setUpWithError() throws {
continueAfterFailure = false
}
override func tearDownWithError() throws {
}
// 追加コード
@MainActor
func testButtonTap() throws {
let app = XCUIApplication()
app.launch()
// TextFieldのUI要素を取ってくる
let textfield = app.textFields["calc_textfield"]
// 0から9までのボタンをタップ(クリック)する
for idx in 0...9 {
let button = app.buttons["button_\(idx)"]
button.tap()
}
// TextFieldの値が期待する値(0123456789)になっているか検証する
XCTAssertEqual(textfield.value as! String , "0123456789")
}
}
テスト実行中
XCodeが自動でUI起動、クリックなどオートメーションで実行されます。
テスト結果
XCTest
やSwift Testing
を同じく、エラーの場合はソースコードエラーと同じ要領で、ソースコードが赤くハイライトされます。
XCUITestの使い方・主要関数一覧
よく使うケースを網羅的にまとめました。主に自分用メモなので辞書的な使い方を考えています。
iPhone/iPad端末を操作する
iOS、iPadOSのテストの際に、デバイス自体を操作する関数が用意されています。
XCUIDevice.shared
で現在使われているエミュレータの情報を取得して操作します
let device = XCUIDevice.shared
// デバイスの向き
device.orientation = .landspaceLeft
// 物理ボタンの動作
device.press(.home)
関数 | 用途 |
---|---|
press(_:) |
端末の物理ボタンを押す。.action 、.camera 、.home 、.volumeUp 、.volumeDown の5種類が設定可能 |
UI要素を選択する
大きく以下の関数が提供されています。
使い方はXCUIApplication
またはXCUIElement
を返す変数に対して処理します
let app = XCUIApplication()
app.launch()
let buttons = app.children(matching: .button)
関数 | 用途 |
---|---|
children(matching: ) |
マッチする子要素を返す |
descendants(matching: ) |
マッチする子孫を返す |
accessibilityIdentifier()を指定したUI要素を取得する
使い方は基本形の記載通りです
let app = XCUIApplication()
app.launch()
let textfield = app.textFields["calc_textfield"]
要素取得する関数一覧
- touchBars
- groups
- windows
- sheets
- drawers
- alerts
- dialogs
- buttons
- radioButtons
- radioGroups
- checkBoxes
- disclosureTriangles
- popUpButtons
- comboBoxes
- menuButtons
- toolbarButtons
- popovers
- keyboards
- keys
- navigationBars
- tabBars
- tabGroups
- toolbars
- statusBars
- tables
- tableRows
- tableColumns
- outlines
- outlineRows
- disclosedChildRows
- browsers
- collectionViews
- sliders
- pageIndicators
- progressIndicators
- activityIndicators
- segmentedControls
- pickers
- pickerWheels
- switches
- toggles
- links
- images
- icons
- searchFields
- scrollViews
- scrollBars
- staticTexts
- textFields
- secureTextFields
- datePickers
- textViews
- menus
- menuItems
- menuBars
- menuBarItems
- maps
- webViews
- steppers
- incrementArrows
- decrementArrows
- tabs
- timelines
- ratingIndicators
- valueIndicators
- splitGroups
- splitters
- relevanceIndicators
- colorWells
- helpTags
- mattes
- dockItems
- rulers
- rulerMarkers
- grids
- levelIndicators
- cells
- layoutAreas
- layoutItems
- handles
- otherElements
- statusItems
UIイベント関連
大きく以下の関数が提供されています。
使い方はXCUIElementQuery
を返す変数に対して処理します
let button: XCUIElementQuery = app.buttons["my_identifier"]
button.tap()
関数 | 用途 |
---|---|
tap() |
1本の指で1回押す動作を行う |
doubleTap() |
1本の指で2回押す動作を行う |
twoFingerTap() |
2本の指でタップする動作を行う |
swipeLeft() |
左にスワイプする動作を行う |
swipeRight() |
右にスワイプする動作を行う |
swipeUp() |
上にスワイプする動作を行う |
swipeDown() |
下にスワイプする動作を行う |
pitch() |
2本の指で広げる動作を行う |
rotate(_:withVelocity:) |
2本の指で回転する動作を行う |
UIのライフサイクルを操作する
大きく以下の関数が提供されています。
使い方はXCUIApplication
を返す変数に対して処理します
let app: XCUIApplication = XCUIApplication()
app.launch()
関数 | 用途 |
---|---|
launch() |
アプリケーションを起動する |
activate() |
アプリケーションを再起動する(iOSアプリのようにサスペンドしている) |
tarminate() |
アプリケーションを終了する |
テスト結果を永続化する
XCTAttachment
を使います。
func testAttachment() {
let attachment = XCTAttachment(string: "ここにテスト結果の文字列などを格納する")
attachment.name = "MainView"
attachment.lifetime = .keepAlways // テスト成功時も記録に残す
add(attachment)
}
テスト結果のキャプチャを取る
XCUIScreenshot
とXCTAttachment
を組み合わせて使います。
キャプチャファイルはReport
ビュー(Command+9)で確認できます
func testTakeScreenshots() {
let app = XCUIApplication()
app.launch()
let windowScreenshot = app.windows.firstMatch.screenshot()
let attachment = XCTAttachment(screenshot: windowScreenshot)
attachment.name = "MainView"
attachment.lifetime = .keepAlways // テスト成功時も記録に残す
add(attachment)
}
Discussion