ども。LDI品質管理部の仙波です。
スマホアプリの自動テストを担当しています。
最近は基本的にリモートワークですが、気分転換に商店街まで散歩してたい焼きを買って帰るのが楽しみです。
前回はXPathを使って画面要素を柔軟に取得する方法を紹介しました。
その際、XPathの使用は他の方法に比べてパフォーマンスに問題があるという通説があることに触れました。
問題があるっていうけど、どれくらいインパクトがある話なの?と言われると自分でも明確に説明できないことに気づいたので、今回は簡単なパフォーマンス計測用のコードを作成し、どれくらい遅いのか計測してみました。
パフォーマンス計測(Androidの場合)
計測に使用したコード
import io.appium.java_client.MobileElement import io.appium.java_client.android.AndroidDriver import org.apache.commons.lang3.time.StopWatch import org.junit.jupiter.api.Test import org.openqa.selenium.remote.DesiredCapabilities import java.net.URL class Performance { private fun getAppiumDriver(): AndroidDriver<MobileElement> { // DesiredCapabilities val capabilities = DesiredCapabilities() capabilities.setCapability("automationName", "UiAutomator2") capabilities.setCapability("platformName", "Android") capabilities.setCapability("platformVersion", "10") capabilities.setCapability("appPackage", "com.android.settings") capabilities.setCapability("appActivity", "com.android.settings.Settings") // appiumServerAddress val appiumServerAddress = URL("http://127.0.0.1:4723/wd/hub") // appiumDriver val appiumDriver = AndroidDriver<MobileElement>(appiumServerAddress, capabilities) return appiumDriver } private val loopCount = 10 @Test fun performance() { val d = getAppiumDriver() d.findElementsById("com.android.settings:id/main_content") val sw = StopWatch() val results1 = mutableListOf<Long>() for (i in 1..loopCount) { val time = measureTime(sw) { d.findElementById("com.android.settings:id/main_content") } results1.add(time) println("byId $time") } val results2 = mutableListOf<Long>() for (i in 1..loopCount) { val time = measureTime(sw) { d.findElementByXPath("//*[@resource-id='com.android.settings:id/main_content']") } results2.add(time) println("byXPath $time") } println("--- results ---") println("byId average:${results1.average()}/min:${results1.minOrNull()}/max:${results1.maxOrNull()}") println("byXPath average:${results2.average()}/min:${results2.minOrNull()}/max:${results2.maxOrNull()}") } private fun measureTime(sw: StopWatch, proc: () -> Unit): Long { sw.reset() sw.start() proc() sw.stop() return sw.time } @Test fun performance2() { val d = getAppiumDriver() d.findElementsById("com.android.settings:id/main_content") val sw = StopWatch() val results1 = mutableListOf<Long>() for (i in 1..loopCount) { val time = measureTime(sw) { d.findElementByClassName("android.widget.ScrollView") } results1.add(time) println("byClass $time") } val results2 = mutableListOf<Long>() for (i in 1..loopCount) { val time = measureTime(sw) { d.findElementByXPath("//*[@class='android.widget.ScrollView']") } results2.add(time) println("byXPath $time") } println("--- results ---") println("byClass average:${results1.average()}/min:${results1.minOrNull()}/max:${results1.maxOrNull()}") println("byXPath average:${results2.average()}/min:${results2.minOrNull()}/max:${results2.maxOrNull()}") } }
計測結果
むしろXPathの方が平均的に速く、通説を覆す結果となりました。(MacでJava/KotlinでUIAutomator2の場合)
単位はmsです。
byId vs byXPath
byId 49 byId 157 byId 527 byId 37 byId 37 byId 173 byId 145 byId 27 byId 29 byId 35 byXPath 128 byXPath 65 byXPath 67 byXPath 59 byXPath 59 byXPath 57 byXPath 226 byXPath 110 byXPath 73 byXPath 59 --- results --- byId average:121.6/min:27/max:527 byXPath average:90.3/min:57/max:226
byClass vs byXPath
byClass 66 byClass 353 byClass 323 byClass 32 byClass 30 byClass 34 byClass 235 byClass 80 byClass 34 byClass 34 byXPath 132 byXPath 58 byXPath 58 byXPath 63 byXPath 53 byXPath 51 byXPath 64 byXPath 278 byXPath 47 byXPath 52 --- results --- byClass average:122.2/min:30/max:354 byXPath average:85.6/min:47/max:278
この結果からはbyIdやbyClassの方がbyXPathよりも高速であると言うことはできず、むしろbyXPathの方が安定して速いと言えます。
ただし、1項目あたり50ms以内の差です。
パフォーマンス計測( iOSの場合)
計測に使用したコード
import io.appium.java_client.AppiumDriver import io.appium.java_client.MobileElement import io.appium.java_client.ios.IOSDriver import org.apache.commons.lang3.time.StopWatch import org.junit.jupiter.api.Test import org.openqa.selenium.remote.DesiredCapabilities import java.net.URL class PerformanceIos { private fun getAppiumDriver(): AppiumDriver<MobileElement> { // DesiredCapabilities val capabilities = DesiredCapabilities() capabilities.setCapability("automationName", "XCUITest") capabilities.setCapability("platformName", "iOS") capabilities.setCapability("platformVersion", "14.3") capabilities.setCapability("deviceName", "iPhone 12 Pro") capabilities.setCapability("simpleIsVisibleCheck", "true") capabilities.setCapability("useJSONSource", "true") capabilities.setCapability("bundleId", "com.apple.Preferences") // appiumServerAddress val appiumServerAddress = URL("http://127.0.0.1:4723/wd/hub") // appiumDriver val appiumDriver = IOSDriver<MobileElement>(appiumServerAddress, capabilities) return appiumDriver } private val loopCount = 10 @Test fun performance() { val d = getAppiumDriver() d.findElementById("General") val sw = StopWatch() val results1 = mutableListOf<Long>() for (i in 1..loopCount) { val time = measureTime(sw) { d.findElementById("General") } results1.add(time) println("byId $time") } val results2 = mutableListOf<Long>() for (i in 1..loopCount) { val time = measureTime(sw) { d.findElementByXPath("//*[@name='General']") } results2.add(time) println("byXPath $time") } println("--- results ---") println("byId average:${results1.average()}/min:${results1.minOrNull()}/max:${results1.maxOrNull()}") println("byXPath average:${results2.average()}/min:${results2.minOrNull()}/max:${results2.maxOrNull()}") } private fun measureTime(sw: StopWatch, proc: () -> Unit): Long { sw.reset() sw.start() proc() sw.stop() return sw.time } @Test fun performance2() { val d = getAppiumDriver() d.findElementByClassName("XCUIElementTypeTable") val sw = StopWatch() val results1 = mutableListOf<Long>() for (i in 1..loopCount) { val time = measureTime(sw) { d.findElementByClassName("XCUIElementTypeTable") } results1.add(time) println("byClass $time") } val results2 = mutableListOf<Long>() for (i in 1..loopCount) { val time = measureTime(sw) { d.findElementByXPath("//*[@type='XCUIElementTypeTable']") } results2.add(time) println("byXPath $time") } println("--- results ---") println("byClass average:${results1.average()}/min:${results1.minOrNull()}/max:${results1.maxOrNull()}") println("byXPath average:${results2.average()}/min:${results2.minOrNull()}/max:${results2.maxOrNull()}") } }
計測結果
XPathを使用すると常に遅く、通説通りの結果となりました。(MacでJava/KotlinでXCUITestの場合)
単位はmsです。
byId vs byXPath
byId 165 byId 182 byId 177 byId 163 byId 141 byId 147 byId 141 byId 141 byId 130 byId 127 byXPath 508 byXPath 487 byXPath 488 byXPath 496 byXPath 517 byXPath 513 byXPath 549 byXPath 602 byXPath 601 byXPath 518 --- results --- byId average:151.5/min:127/max:182 byXPath average:527.9/min:487/max:602
byClass vs byXPath
byClass 109 byClass 94 byClass 92 byClass 102 byClass 104 byClass 100 byClass 115 byClass 109 byClass 107 byClass 104 byXPath 277 byXPath 267 byXPath 268 byXPath 327 byXPath 295 byXPath 264 byXPath 259 byXPath 257 byXPath 283 byXPath 263 --- results --- byClass average:103.7/min:92/max:115 byXPath average:276.0/min:257/max:327
パフォーマンス計測結果(サマリー)
計測結果を表にまとめます。
- 平均は四捨五入
- 青字が速い方、赤字が遅い方
OS | ケース | 検索する属性 | メソッド | 平均(ms) | 最早(ms) | 最遅(ms) |
---|---|---|---|---|---|---|
Android | 1 | resource-id | byId | 122 |
27 |
527 |
Android | 1 | resource-id | byXPath | 90 |
57 |
226 |
Android | 2 | className | byClass | 104 |
92 |
115 |
Android | 2 | className | byXPath | 86 |
47 |
278 |
iOS | 3 | name | byId | 152 |
127 |
182 |
iOS | 3 | name | byXPath | 523 |
487 |
602 |
iOS | 4 | type | byClass | 104 |
92 |
115 |
iOS | 4 | type | byXPath | 276 |
257 |
327 |
なぜAndroidとiOSで逆の結果となるのか
要素取得の速度はAndroidの方が常にiOSよりも速いので、AndroidのUIAutomator2は十分パフォーマンスチューニングされているのではないかと思われます。XPathを使用した方が良い結果が出る場合があるのは面白いところです。
iOSのXCUITestはUIAutomator2と比較すると常にもっさりしており、十分チューニングされていない印象を受けます。(全く同じ仕様のAndroidアプリとiOSアプリに対して同じ内容のテストを実行すると、常にAndroidの方が速いことが経験上わかっています。)
iOSについては通説が当てはまるので、XPathをできるだけ使用しないようにした方が、実行時間は短縮できそうです。
まとめ
- XPathによる要素取得は遅いという通説について、実験の結果、iOS(XCUITest)ではその通りですが、Android(UIAutomator2)では当てはまらないことがわかりました
- Android(UIAutomator2)については、XPathを使ってもパフォーマンス上の問題はなさそうです
- iOS(XCUITest)はXPathを使うと遅いですが、使わなくてもAndroidよりパフォーマンスが悪いのでもっと頑張って欲しいです
関連記事
こちらの記事もオススメです。
qiita.com