UIテストは、モバイルアプリケーションのユーザーインターフェース要素の表示とインタラクションの正確さを検証します—ボタン、テキストフィールド、リスト、ナビゲーションコンポーネントなど。ビジネスロジックを検証する単体テストとは異なり、UIテストはユーザーのアクション—タップ、スワイプ、テキスト入力—をシミュレートし、インターフェースの反応を検証します。Android Developers, 2024の調査によると、UIテストは重要なユーザーシナリオの70%をカバーし、論理的なチェックでは検出できないレイアウトのデフェクトを見つけることができます。
まとめ
UIテストは、テストコードがアプリケーションのグラフィカルインターフェースと実際のユーザーと同じようにインタラクションする自動化検証の一種です。テストは画面上の要素—ボタン、テキストフィールド、リスト—を見つけ、それに対してアクションを実行し、予期されるインターフェースの反応を検証します。例えば、間違ったパスワードを入力した後、UIテストは正しいテキストのエラーメッセージが画面上に表示されるかを検証します。
UIテストと他の自動化の最大の違いは、アプリケーションの内部APIではなく、OSのアクセシビリティレイヤーを通じて動作することです。これにより、UIテストはユーザーやスクリーンリーダーと完全に同じようにインターフェースを視えます。これにより、UIテストは機能だけでなく、要素のアクセシビリティ(WCAG要件への適合)も検証できます。
JetBrains Developer Ecosystem 2023の調査によると、58%のモバイルチームがCI/CDパイプラインでUIテストを使用しています。コマーシャルプロジェクトにおけるUIテストの平均カバレッジは30–40%です。UIテストを導入しているプロジェクトは、インターフェースのクラッシュに関するアプリストアでの悪評価が25%減少しています。
UIテストと単体テストの最大の違いは抽象度です。単体テストは、AndroidやiOSのフレームワークから離された個々のクラスや関数を扱います。これらはエミュレーターを起動せずにJVM上で実行され、ミリ秒で終了します。UIテストは実機またはエミュレーターで実行され、システムサービスとインタラクションし、シナリオごとに数秒から数分を要します。
テストの対象となる目的も異なります。UIテストはエンドツーエンドのユーザーシナリオ—登録、注文、検索—を検証します。単体テストはビジネスロジック(計算、バリデーション、データ変換)をカバーします。UIテストは税金計算の正確さを検証せず、総額が画面に表示されるかどうかを検証します。計算そのものは単体テストで検証されます。
Google Testing Blog (2020)によると、プロジェクトにおけるテストの最適割合は、テストピラミッドのルールに従い、70%が単体テスト、20%が統合テスト、10%がUIテストです。この割合をUIテスト側に破ると、実行時間が増加し、UIテストが画面レイアウトの変更に敏感なため、テストスイートが脆弱になります。
Androidでは、主導的なフレームワークはEspressoです—Googleが提供するライブラリで、AndroidX Testに組み込まれています。Espressoは自動的にUIスレッドとシンクロナイズし、次のアサーションを実行する前にアニメーションとバックグラウンドタスクの完了を待ちます。Jetpack Composeでは、伝統的なビュー識別子の代わりにセマンティックノードを使用するCompose UI Test拡張が使用されます。
iOSでは、主なツールはXCUITestで、Xcodeの一部です。テストはSwiftで書かれ、要素を見つけるためにAccessibility識別子を使用します。XCUITestはレコード機能によるテスト記録と、xcodebuildを介したCIシステムとの統合をサポートしています。クロスプラットフォームプロジェクトでは、WebDriverプロトコルに基づき、最小限のコード変更でAndroidとiOSの両方で同じテストを実行できるAppiumが使用されます。
Espressoは、onViewとリソースID識別子を介して伝統的なビューシステムで動作します。Compose UI Testはセマンティックレイヤーを使用し、テストがビュー階層に依存することを減らします。例えば、Espressoでボタンを見つける場合: onView(withId(R.id.submit))、Composeでは: onNodeWithTag(“submit”)です。Composeテストは自動的に再構成を処理し、明示的なアイドル状態の待機が不要です。
XCUITestは入口点としてXCUIApplicationを使用します。各インターフェース要素は、Accessibilityプロパティ—プログラムアクセス用のaccessibilityIdentifierとVoiceOver用のaccessibilityLabel—を介して検索されます。このフレームワークは、Xcodeのレコード機能によるテスト記録をサポートしています—ディベロッパーがシミュレーターでアクションを実行すると、Xcodeがテストコードを生成します。完了したテストはxcodebuild testを介して実行されます。
AppiumはWebDriverプロトコルに基づき、Java、Python、JavaScriptなど様々な言語をサポートしています。要素検索策略は、id、xpath、class name、accessibility idを含みます。AppiumはサーバーのインストールとDesired Capabilities(platformName、deviceName、appPackage)の設定が必要です。代替案としては、YAMLシナリオを使用し、テストコードのコンパイルが不要なMaestroがあります。
3つの異なるフレームワークで同じシナリオ—アプリにログイン—のUIテストを見てみましょう: Android用Espresso、iOS用XCUITest、クロスプラットフォームアプローチ用Appium。シナリオ: ログインとパスワードを入力し、ログインボタンを押し、ようこそメッセージが表示されることを確認します。
Espressoテストは、onViewを使って識別子で要素を見つけ、performでアクションを実行します。isDisplayedマッチャーとのcheckメソッドは、要素が画面上に表示されていることを確認します。
@RunWith(AndroidJUnit4::class)
class LoginUiTest {
@Rule
@JvmField
val composeTestRule = createComposeRule()
@Test
fun login_withValidCredentials_showsWelcome() {
composeTestRule
.onNodeWithTag("emailField")
.performTextInput("user@example.com")
composeTestRule
.onNodeWithTag("passwordField")
.performTextInput("secret123")
composeTestRule
.onNodeWithTag("loginButton")
.performClick()
composeTestRule
.onNodeWithText("ようこそ、ユーザーさん!")
.assertIsDisplayed()
}
}
XCUITestは、Accessibility識別子を介してインターフェース要素にアクセスするためにXCUIApplicationを使用します。tap()とexistsメソッドは、インタラクションと検証を提供します。
class LoginUITests: XCTestCase {
let app = XCUIApplication()
override func setUp() {
continueAfterFailure = false
app.launch()
}
func testLogin_withValidCredentials_showsWelcome() {
app.textFields["emailField"].tap()
app.textFields["emailField"].typeText("user@example.com")
app.secureTextFields["passwordField"].tap()
app.secureTextFields["passwordField"].typeText("secret123")
app.buttons["loginButton"].tap()
XCTAssertTrue(app.staticTexts["Welcome, User!"].exists)
}
}
第一の原則は—要素を見つけるためにテキストラベルではなくAccessibility識別子を使用することです。ボタンのテキストはローカライゼーションによって変わる可能性がありますが、識別子は一定です。AndroidではこれはcontentDescriptionプロパティで、iOSではaccessibilityIdentifierです。このアプローチにより、テストがインターフェース言語から独立し、コピーライティングが変わっても保守コストが削減されます。
sleep()や固定延結を避ける—フレームワークの組み込み待機メカニズムを使用します。Espressoは自動的にアニメーションとバックグラウンドタスクの完了を待ちます。XCUITestはタイムアウト付きのXCTAssertTrueを提供します。明示的な中断は、特にCI環境の遅いデバイスで、テストを遅くし不安定にします。
重要度によってテストをグループ化:スモークテスト(3–5の主なシナリオ)はコミットごとに実行され、全UIテストスイートはリリース前に実行されます。Google Testing Blog (2022)によると、CIで30分以上かかるUIテストは実行頻度が40%減少し、リグレッション早期発見ツールとしての効果が低下します。
UIテストにはいくつかの制約があります。レイアウト変更に対する敏感性: 識別子、階層、要素タイプを変更すると、機能が変わらなくてもテストが失敗します。解決策は、要素セレクターを独立したクラスにまとめるPage Objectパターンを使用することです。レイアウトが変わった場合、数十のテストではなく、一つのPage Objectファイルだけを修正します。
実行時間: 実機またはエミュレーターでの実行は、単体テストより10–50倍の時間がかかります。解決策は、Firebase Test LabやAWS Device Farmを介して複数デバイスでUIテストを並列実行することです。不安定さ(flakiness)は、アニメーション、ネットワーク延結、エミュレーターの状態によって引き起こされるCI実行の問題です。不安定さに対処するために、失敗テストの自動再実行や各テストシナリオの安定性分析が使用されます。
よくある質問
平均的な画面では3–5つのUIテストで十分です: ハッピーパス、エラー検証、空の状態、向き変更、Accessibilityチェック。複数の状態をもつ複雑な画面—注文フォーム、設定—では、主なシナリオを完全にカバーするために10–15のテストが必要になる場合があります。
はい、AppiumとMaestroは両プラットフォームで同じシナリオを実行できます。ただし、ネイティブフレームワーク(EspressoとXCUITest)は、より良い安定性、スピード、およびWebDriverプロキシを通じては利用できないプラットフォーム固有機能へのアクセスを提供します。
Composeでは、セマンティックマッチャーを持つCompose UI Testライブラリが使用されます: onNodeWithText、onNodeWithTag、onNodeWithContentDescription。Composeのセマンティックレイヤーはビュー階層を抽象化するため、伝統的なビューシステム用のEspressoに比べてテストが脆弱になりにくくなります。
基本的なUIテストはCIのエミュレーターで実行されます—速くて安いです。リリース前の最終検証は、実機の特性(異なる解像度、OSバージョン、パフォーマンス)を考慮するために、Firebase Test Labを介して物理デバイスで実行することをお勧めします。
複数デバイスでの並列実行を使用し、デベロッパーオプションでエミュレーターのアニメーションを無効にし、モジュール化されたテストアーキテクチャを構築し、スモークスイートをコミットごとに実行し、全リグレッション実行はスケジュールに従ってまたはリリース前に実行します。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。