1. はじめに
こんにちは、NX開発推進部iOSアプリ開発グループの後藤です。
私たちのチームでは、自動テストにMagicPodを利用しています。
MagicPodは、モバイルアプリやウェブアプリのテストを自動化するためのAIテスト自動化プラットフォームです。
今回はMagicPodについての詳細な説明は割愛します。
当初、MagicPodのテストでxPathロケータを用いてUI要素を検出させていましたが、UI要素は存在しているものの検出できずにテストが失敗する問題が発生していました。調査を進める中で、テストの安定性をより高めるためには、UI要素の検出方法をxPathロケータからaccessibility idロケータに変更することが効果的であることがわかりました。
MagicPodは、内部的にアプリのシステム情報を利用して操作対象のUI要素を特定するため、要素を特定しやすいユニークIDがなかったり、変更されてしまったりすると、テスト失敗の原因となります。ユニークIDを各UI要素に付与し、変更しないようにすることで、安定したテスト運用が可能になります。
accessibility idロケータを設定するメリットをまとめると以下のようになります。
- テストの安定性向上: 要素が一意に識別されるため、テストの際に正確な要素を検出しやすくなります。
- メンテナンスが容易: MagicPodのテストケースがUIの変更に強くなり、メンテナンスが容易になります。
- テストの高速化: xPathロケータを使用するのと比べて、テストの実行が速くなります。
- xpathロケータは低速になりがちなので、できるだけaccessibility idやios class chainロケータを使用するとよい
accessibility idロケータを利用するためには、アプリ内の各UI要素に対して一意のaccessibilityIdentifierを設定する必要があります。
そこで、この記事ではUIViewにaccessibilityIdentifierを簡単に設定する方法を紹介します。
2. accessibilityIdentifierの設定方法
accessibilityIdentifierを設定する方法は、XcodeのIdentity Inspectorで設定する方法とコードで設定する2種類の方法があります。
Identity Inspectorで設定する
Identity Inspectorで設定する方法では、Xcode右側ペインのShow the Identity InspectorのAccessibilityの項目から設定できます。
しかしこの方法では、accessibilityIdentifierを手動で設定する必要があり、管理もしづらいという問題点があります。
また、UITableViewCellやUICollectionViewCellのように、動的に生成される要素に関しては設定することができません。
そこで、これらの問題を解決するためにプロトコルを利用して、コードベースでaccessibilityIdentifierを設定する方法を次に紹介します。
プロトコルを利用してaccessibilityIdentifierを設定する
UIViewのサブクラスでaccessibilityIdentifierを設定するプロトコルとその拡張を作成しました。
import UIKit protocol AccessibilityIdentifierSettable { func setAccessibilityIdentifiers(_ target: UIView?) } extension AccessibilityIdentifierSettable where Self: UIView { func setAccessibilityIdentifiers(_ target: UIView? = nil) { #if DEBUG let mirror = Mirror(reflecting: target ?? self) // 最も近いViewControllerのクラス名を取得 let viewControllerClassName = getViewControllerName() for child in mirror.children { guard let view = child.value as? UIView, let propertyName = child.label? .replacingOccurrences(of: "$__lazy_storage_$_", with: "") else { continue } var accessibilityIdentifierElements: [String] = [propertyName] // indexPathを取得して一意になるようにする if let collectionViewCell = self as? UICollectionViewCell, let collectionView = collectionViewCell.superview as? UICollectionView, let indexPath = collectionView.indexPath(for: collectionViewCell) { accessibilityIdentifierElements.append(indexPath.description) } // indexPathを取得して一意になるようにする if let tableViewCell = self as? UITableViewCell, let tableView = tableViewCell.superview as? UITableView, let indexPath = tableView.indexPath(for: tableViewCell) { accessibilityIdentifierElements.append(indexPath.description) } // accessibilityIdentifierにクラス名を付与し重複を防ぐ view.accessibilityIdentifier = viewControllerClassName + "-" + accessibilityIdentifierElements.joined() } #endif } } |
コード解説
- Mirror API: Mirrorオブジェクトのプロパティやメソッドに対する情報を取得できます。 Mirrorを使用して、UIViewのプロパティにアクセスし、各プロパティに適切なaccessibilityIdentifierを設定します。
- accessibilityIdentifierを一意にする工夫: CollectionViewやTableViewのCellに対しては、indexPathをaccessibilityIdentifierに追加して一意になるようにします。他にも、複数の画面で同じUIが使われていてもaccessibilityIdentifierを一意にするために、UIViewを使用しているViewControllerの名前を付与するようにしています。
また、ViewControllerの名前を取得するために以下のメソッドをUIViewのExtensionとして定義しました。
extension UIView { func getViewControllerName() -> String { var responder: UIResponder? = self let defaultControllerName = "UnknownViewController" while responder != nil { if let viewController = responder as? UIViewController { let className = String(describing: type(of: viewController)) return className.components(separatedBy: ".").last ?? defaultControllerName } responder = responder?.next } return defaultControllerName } } |
3. 実際の適用方法
以下のサンプルコードは、Cellのクラスを先ほどのプロトコルに準拠させて、accessibilityIdentifierを設定する例です。
accessibilityIdentifierを付与したいViewの
layoutSubviewsのタイミングでメソッドを呼ぶことで、Cellの再利用時にaccessibilityIdentifierのindexPathが書き変わってしまう問題を防いでいます。
CellのindexPathが [0, 0]で HogeViewControllerで使われていた場合、 titleLabelには HogeViewController-titleLabel[0, 0]、 imageViewには HogeViewController-imageView[0, 0]というaccessibilityIdentifierが付与されます。
final class CustomTableViewCell: UITableViewCell, AccessibilityIdentifierSettable { private let titleLabel = UILabel() private let imageView = UIImageView() override func layoutSubviews() { super.layoutSubviews() setAccessibilityIdentifiers() } } |
確認方法
実際にXcodeのView HierarchyでaccessibilityIdentifierが設定されていることを確認できます。
これにより、正しく設定されたかどうかを確認できます。
4. accessibilityIdentifierの設定によるテスト実行の効果
accessibilityIdentifierを一意に設定することで、xPathロケータではなく、accessibility idロケータを選択できるようになりました。その結果、指定したUI要素を検出できずにテストが失敗する問題が減り、より安定したテスト運用が可能になりました。
5. まとめ
本記事では、accessibilityIdentifierを設定することのメリットや、プロトコルを使ってaccessibilityIdentifierを設定する方法について紹介しました。
accessibilityIdentifierを一意に設定することで、テストの安定性が向上します。
また、プロトコルを利用することで、accessibilityIdentifierの名前を毎回考える手間が省け、プロパティが増えた場合でもaccessibilityIdentifierが自動的に付与されるため、管理が簡単になります。
この記事がテストの運用に役に立てば幸いです。
この記事を書いた人
最近書いた記事
- 2024.06.14SwiftでUIViewにaccessibilityIdentifierを簡単に設定する
- 2023.12.15【iOS】音楽アプリで使えるCarPlayのUIについて
- 2023.10.31【Maker Faire Tokyo 2023】レコチョクマミン(テルミン風楽器)を作ってみた
- 2023.09.15Function callingを使ってアプリの画面使用率を出してみた