1. 程式人生 > >UiAutomator2.0升級填坑記

UiAutomator2.0升級填坑記

新建 意思 原來 也有 sha ogl selector 輸入文字 apk

UiAutomator2.0升級填坑記

SkySeraph May. 28th 2017

Email:[email protected]

更多精彩請直接訪問SkySeraph個人站點:www.skyseraph.com

啰嗦

Google Android Developers 在2015年3月就發布了UiAutomator 2.0版本(下文簡稱U2),而公司的核心產品中用到還是UiAutomator老版本(下文簡稱U1),業界用U2的也不是很多,雖然有諸多問題和不便(如高版本OS中不支持Remote Debug等),但大家似乎在茍延殘喘中麻木了。 公司以前也有專家做過研究和探索,畢竟老項目是萬級別的,涉及諸多算法和復雜業務邏輯,遇到幾個問題最終不了了之。於是乎,又到了我手裏了。

重大特性

  1. 基於 Instrumentation,使用Instrumentation test runner即可運行UiAutomator,反之,也即在基於Instrumentation的test中也能使用UiAutomator; 可以獲取應用Context,可以使用Android服務及接口。

  2. 基於 Junit4,測試用例無需繼承於任何父類,方法名不限,使用Annotation進行; U1需要繼承UiAutomatorTestCase,測試方法需要以test開頭.

  3. 與U1的Maven或Ant構建方式不同,U2采用Gradle進行構建; U2輸出為APK,Android工程,而U1為Java工程,輸出jar包。

  4. 新增UiObject2、Until、By、BySelector等接口, 詳細請參考官方文檔。
其中,U2必須明確EditText框才能向裏面輸入文字,U1直接指定父類也可以在子類中輸入文字。

  5. Log日誌輸出變更。U1可以使用System.out.print輸出流回顯至執行端,而U2輸出到Logcat。

  6. 命令運行差異。

adb shell uiautomator runtest xx.jar -c package.name.ClassName
adb shell am instrument -w -r -e class com.xx.xx com.xx.xx.test/ android.support.test.runner.AndroidJUnitRunner

  代碼中運行:

Runtime.getRuntime().exec(“*“)
device.executeShellCommand(“*“)

  命令格式:instrument [options] component,詳細命令格式介紹參考官方文檔。

基礎使用

  • 新建Android Studio工程。

  • gradle中添加依賴。

androidTestCompile(‘com.android.support.test.espresso:espresso-core:2.2.2’{
exclude group: ‘com.android.support’, module: ‘support-annotations’
})
androidTestCompile ‘com.android.support.test:runner:0.5’
androidTestCompile ‘com.android.support.test:rules:0.5’
androidTestCompile ‘com.android.support.test.uiautomator:uiautomator-v18:2.1.2’
androidTestCompile ‘com.android.support:support-annotations:25.3.1’

如需要依賴jar包

androidTestCompile fileTree(dir: ‘libs‘, include: [‘*.jar‘]) 
  • androidTest目錄下添加測試用例。
@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class XXTest {
public Bundle bundle;
public UiDevice device;
private Instrumentation instrumentation;

@BeforeClass
public static void beforeClass() {

}

@Before
public void before() {
    // Initialize UiDevice instance
    instrumentation = InstrumentationRegistry.getInstrumentation();
    device = UiDevice.getInstance(instrumentation);
    bundle = InstrumentationRegistry.getArguments();
    assertThat(device, notNullValue());
}

@After
public void after() {
    L.i(TAG + "After");
}

@AfterClass
public static void afterClass() {
    L.i(TAG + "AfterClass");
}

public Bundle getParams() {
    return bundle;
}
}

采坑記

  • 編譯錯誤.
    這個應該說是官方故意坑爹吧,U2的包名結構發生了變化,所以需要將之前所有用到的U1相關API包名進行替換,具體為

    com.android.uiautomator.core. -> android.support.test.uiautomator.
  • 反射相關.
    這個有點坑爹,還是U2包名結構問題(原項目很多地方用到了反射獲取U1相關類/方法等),除非對原業務非常熟悉,否則只有遇到問題分析時才會發現此坑,讀者以後如果遇到這種類似包結構變化的問題,先不管三七二十一,全局搜索相關字段全局了解再說。當然,如果用到了混淆,也要對應修改。

  • UiAutomatorTestCase相關.
    問題:去除所有測試類中的extend UiAutomatorTestCase後異常。因為原項目依賴於UiAutomatorTestCase中的getPara API來獲取U1命令傳參。
    方案:修改成U2的InstrumentationRegistry.getArguments方式。

  • 日誌相關.
    問題:原項目中業務邏輯依賴System.out.println日誌輸出結果,而U2中System.out.println不會回顯到終端顯示,直接影響之前業務邏輯的執行。
    方案:使用Instrumentation.sentStatus自定義狀態。

  • 屬性文件.
    描述:這個有點意思,原項目為Java工程,有很多屬性配置在properties屬性文件中,大概有20來個properties文件。而對屬性文件的讀取是在一個common的公共jar包中,采取的是Thread.currentThread().getContextClassLoader().getResourceAsStream方式。
    問題:①屬性配置文件無法打包進apk中 ②讀取方式問題
    解決:
    方法1: 將屬性配置文件全部放入assets目錄,然後通過Context().getAssets().open方式讀取;而原先的jar中的公共類,抽離出來作為業務類。
    方法2: 通過gradle自定義打包,將所有屬性配置文件打包進apk中,建議。

  • xml配置文件.
    問題:原工程用到了RPC框架,有xml配置文件,存在與上述properties類似問題。
    解決:gradle自定義打包。

  • 權限問題.
    問題:Android M+對權限進行了升級,本工程需要用到文件存儲權限,僅在Manifest中聲明WRITE_EXTERNAL_STORAGE權限還不夠。
    解決

方法1:

@Before  
public void grantPermission() {  
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {  
        getInstrumentation().getUiAutomation().executeShellCommand("pm grant "+getTargetContext().getPackageName()+"android.permission.WRITE_EXTERNAL_STORAGE");  
    }
}

方法2:通過Rule

@Rule
public final PermissionsRule permissionsRule = new PermissionsRule(
new String[]{  
Manifest.permission.READ_EXTERNAL_STORAGE,
   Manifest.permission.WRITE_EXTERNAL_STORAGE});
  • NoClassDefFoundError問題.

問題:在Android 4.4手機出現NoClassDefFoundError問題,Instrumentation消息為

INSTRUMENTATION_RESULT: longMsg=java.lang.NoClassDefFoundError: org.junit.runner.manipulation.Filter$1 

解決: MultiDex手動拆包A

①gradle文件中添加multiDexKeepProguard

defaultConfig {
    multiDexEnabled true
    multiDexKeepProguard file(‘multidex.pro‘)
}

②multidex文件中添加

-keep class android.support.multidex.** {*;}
-keep public class org.junit.runner.** {*;}
  • 系統簽名問題.
    描述:此問題是後續測試中才發現的,原來的穩定性/兼容性測試上的性能指標參數大部分顯示為空,極其詭異,後面花了很大功夫進行分析發現,很多指標會出現會出現類似Permission Denial : cannot dump 錯誤,獲取不到任何數據。因為原工程需要獲取電池、CPU、內存等等幾十項性能參數指標,有很大一部分是通過sh讀取系統文件等方式獲取的,通過Runtime.getRuntime().exec 或者 ProcessBuilder.start方式, 比如通過dump中的dump battery獲取電池信息,因為dumpsys涉及android:protectionLevel=”signatureOrSystem”,需要 權限,需要聲明android:sharedUserId=”android.uid.system”,需要系統簽名. …
    解決
    系統簽名(無奈之舉).
    采用device.executeShellCommand,註意只支持API 21+.

REFS

    • Google 官方文檔

    • Google 官方Demo

    • How to manage Runtime permissions android marshmallow espresso tests

    • 設置Multidex出現java.lang.NoClassDefFoundError

    • Android Espresso multidex fail

後記

本文首發於skyseraph.com:“UiAutomator2.0升級填坑記”
同步發表/轉載 cnBlogs / …



By SkySeraph-2017

UiAutomator2.0升級填坑記