Android自動化測試--學(xué)習(xí)淺談

前言

想想接觸Android也有三年多的時間了,實際開發(fā)也有兩年的時間了,好像也很少接觸到Android自動化測試,雖然偶有聽說,但也沒有認(rèn)真的學(xué)習(xí)過。相信很多朋友跟我也有一樣的經(jīng)歷,對自動化測試不了解,加上項目沒有要求,認(rèn)為自動化測試價值不高,完全是浪費時間。但實際的情況并不是這樣,前段時間聽一個朋友講了些Android自動化測試,給了我很深的印象。正因為這樣的契機,所以前段時間也花時間學(xué)了Android基本的自動化測試。趁最近剛好有空整理了一下自己的學(xué)習(xí)心得。

大家可以看一下這篇文章,可能會說服你:為什么要進(jìn)行煩人的單元測試?

Android Testing Support Library

在2015年Google I/O大會上,Google放出了一個Android Testing Support Library,該庫提供了大量用于測試 Android 應(yīng)用的框架。此庫提供了一組 API,讓您可以為應(yīng)用快速構(gòu)建何運行測試代碼,包括單元測試 JUnit 4 和功能性用戶界面 (UI) 測試。我們可以從Android Studio IDE或命令行運行使用這些 API 創(chuàng)建的測試。 在測試庫中包含AndroidJUnitRunner類是一個JUnit測試運行器,可讓我們在 Android 設(shè)備上運行 JUnit 3 或 JUnit 4 樣式測試類,包括使用Espresso和UI Automator測試框架的設(shè)備。測試運行器可以將測試軟件包和要測試的應(yīng)用加載到設(shè)備、運行測試并報告測試結(jié)果。所以后面會講到的單元測試和UI測試的詳細(xì)使用,都是基于Android Testing Support Library。

單元測試 JUnit 4

我們在實際項目開發(fā)中的時候,都是需要寫成千上萬個方法或函數(shù),這些函數(shù)的功能可能很強大,也可能是很小一個功能,但我們在程序中使用時都是需要經(jīng)過測試的,保證這一部分功能是正確的。所以說,每編寫完一個函數(shù)之后,都應(yīng)該對這個函數(shù)的方方面面進(jìn)行測試,這樣的測試我們稱之為單元測試。傳統(tǒng)的編程方式,進(jìn)行單元測試是一件很麻煩的事情,我們需要在該程序中調(diào)用你需要測試的方法,并且仔細(xì)觀察運行結(jié)果,看看是否有錯。正因為如此麻煩,所以就有了很多單元測試框架,JUnit 4就是其中一種。

本地單元測試 Local Unit Tests

這種測試運行在本地開發(fā)環(huán)境的Java虛擬機上,也不需要連接Android設(shè)備或者模擬器,因此并無法獲得Android相關(guān)的API,所以只能測試只使用Java API的一些功能。

測試類代碼編寫也很簡單,主要通過一些注解來標(biāo)示,同時可以通過assertXXXX來斷言結(jié)果

public class ExampleUnitTest {
  @Test
  public void addition_isCorrect() throws Exception {
      assertEquals(4, 4);
  }
}

Junit 4注解

  • @Before標(biāo)注setup方法,每個單元測試用例方法調(diào)用之前都會調(diào)用
  • @After標(biāo)注teardown方法,每個單元測試用例方法調(diào)用之后都會調(diào)用
  • @Test標(biāo)注的每個方法都是一個測試用例
  • @BeforeClass標(biāo)注的靜態(tài)方法,在當(dāng)前測試類所有用例方法執(zhí)行之前執(zhí)行
  • @AfterClass標(biāo)注的靜態(tài)方法,在當(dāng)前測試類所有用例方法執(zhí)行之后執(zhí)行
  • @Test(timeout=)為測試用例指定超時時間

斷言
Junit提供了一系列斷言來判斷是pass還是fail

  • assertTrue(condition):condition為真pass,否則fail
  • assertFalse(condition):condition為假pass,否則fail
  • fail():直接fail
  • assertEquals(expected, actual):expected equal actual pass,否則fail
  • assertSame(expected, actual):expected == actual pass,否則fail

設(shè)備單元測試 Instrumented Unit Tests

這種測試方式需要連接Android設(shè)備或模擬器??梢岳肁ndroid框架API,比如測試需要訪問設(shè)備信息(如目標(biāo)應(yīng)用程序的上下文中)或如果他們需要一個Android 相關(guān)的API(如Parcelable或SharedPreferences對象)。在使用上也很簡單,相比本地單元測試該測試類必須以 @RunWith(AndroidJUnit4.class) 注解作為前綴。

@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
    @Test
    public void useAppContext() throws Exception {
        // Context of the app under test.
        Context appContext = InstrumentationRegistry.getTargetContext();
        assertEquals("top.qingningshe.test", appContext.getPackageName());
    }
}

相比Local Unit Tests 多了訪問設(shè)備信息、測試篩選

訪問設(shè)備信息

我們可以使用 InstrumentationRegistry
類訪問與測試運行相關(guān)的信息。此類包括 Instrumentation對象、目標(biāo)應(yīng)用Context對象、測試應(yīng)用Context對象,以及傳遞到測試中的命令行參數(shù)。

測試篩選

  • @RequiresDevice:指定測試僅在物理設(shè)備而不在模擬器上運行。
  • @SdkSupress:禁止在低于給定級別的 Android API 級別上運行測試。例如,要禁止在低于 18 的所有 API 級別上運行測試,請使用注解 @SDKSupress(minSdkVersion=18)。
  • @SmallTest、@MediumTest和@LargeTest:指定測試的運行時長以及運行頻率。

UI測試

Espresso

Espresso 測試框架提供了一組 API 來構(gòu)建 UI 測試,用于測試應(yīng)用中的用戶流。利用這些 API,您可以編寫簡潔、運行可靠的自動化 UI 測試。Espresso 非常適合編寫白盒自動化測試,其中測試代碼將利用所測試應(yīng)用的實現(xiàn)代碼詳情。

Espresso 測試框架的主要功能包括:

  • 靈活的 API,用于目標(biāo)應(yīng)用中的視圖和適配器匹配。
  • 一組豐富的操作 API,用于自動化 UI 交互。
  • UI 線程同步,用于提升測試可靠性。

要求 Android 2.2(API 級別 8)或更高版本。

視圖匹配

利用Espresso.onView()方法,您可以訪問目標(biāo)應(yīng)用中的 UI 組件并與之交互。此方法接受Matcher參數(shù)并搜索視圖層次結(jié)構(gòu),以找到符合給定條件的相應(yīng)View實例。您可以通過指定以下條件來優(yōu)化搜索:

  • 視圖的類名稱 onView(withClassName());
  • 視圖的內(nèi)容描述 onView(withContentDescription());
  • 視圖的ID onView(withId());
  • 在視圖中顯示的文本 onView(withText());

更多的可以查看ViewMatchers。如果搜索成功,onView()方法將返回一個引用,讓您可以執(zhí)行用戶操作并基于目標(biāo)視圖對斷言進(jìn)行測試。

適配器匹配

AdapterView布局中,布局在運行時由子視圖動態(tài)填充。如果目標(biāo)視圖位于某個布局內(nèi)部,而該布局是從AdapterView(例如ListViewGridView)派生出的子類,則onView()方法可能無法工作,因為只有布局視圖的子集會加載到當(dāng)前視圖層次結(jié)構(gòu)中。因此,需要使用Espresso.onData()方法訪問目標(biāo)視圖元素。Espresso.onData()方法將返回一個引用,讓您可以執(zhí)行用戶操作并根據(jù)AdapterView中的元素對斷言進(jìn)行測試。

//點擊spinner
onView(withId(R.id.spinner)).perform(click());
//點擊adpaterviewer中類型為String 并且內(nèi)容為test的文本,
onData(allOf(is(instanceOf(String.class)),is("test"))).perform(click());

操作API

在上面的一段代碼中,我們用到了perform(click()),那么除了click()方法還有其他功能強大的方法可以供我們使用,下面列舉一些常用的方法:

  • click():返回一個點擊動作,Espresso利用這個方法執(zhí)行一次點擊操作,就和我們自己手動點擊按鈕一樣。
  • clearText():返回一個清除指定view中的文本action,在測試EditText時用的比較多。
  • swipeLeft():返回一個從右往左滑動的action,這個在測試ViewPager時特別有用。
  • swipeRight():返回一個從左往右滑動的action,這個在測試ViewPager時特別有用。
  • swipeDown():返回一個從上往下滑動的action。
  • swipeUp():返回一個從下往上滑動的action。
  • closeSoftKeyboard():返回一個關(guān)閉輸入鍵盤的action。
  • pressBack():返回一個點擊手機上返回鍵的action。
  • doubleClick():返回一個雙擊action
  • longClick():返回一個長按action

更多的可以查看ViewActions。

校驗結(jié)果

調(diào)用ViewInteraction.check()DataInteraction.check()方法,可以判斷UI元素的狀態(tài),如果斷言失敗,會拋出AssertionFailedError異常。

  • doesNotExist:斷言某一個view不存在。
  • matches:斷言某個view存在,且符合一列的匹配。
  • selectedDescendentsMatch:斷言指定的子元素存在,且他們的狀態(tài)符合一些列的匹配。
onView(withId(R.id.textview)).check(matches(withText("test")));

UI 線程同步

Espresso 的核心是它可以與待測應(yīng)用無縫同步測試操作的能力。默認(rèn)情況下,Espresso 會等待當(dāng)前消息隊列中的 UI 事件執(zhí)行(默認(rèn)是 AsyncTask)完畢再進(jìn)行下一個測試操作。這應(yīng)該能解決大部分應(yīng)用與測試同步的問題。然而,應(yīng)用中有一些執(zhí)行后臺操作的對象(比如與網(wǎng)絡(luò)服務(wù)交互)通過非標(biāo)準(zhǔn)方式實現(xiàn);例如:直接創(chuàng)建和管理線程,以及使用自定義服務(wù)。慶幸的是 Espresso 仍然可以同步測試操作與你的自定義資源。

以下是我們需要完成的:

  • 實現(xiàn) ?IdlingResource 接口并暴露給測試。
  • 通過調(diào)用ResourceCallback..onTransitionToIdle()通知Espresso。

需要注意的是 IdlingResource 接口是在待測應(yīng)用中實現(xiàn)的,所以你需要添加依賴:

 compile 'com.android.support.test.espresso:espresso-idling-resource:2.2.2'

下面我們看看官方的例子是如何實現(xiàn)的

public final class CountingIdlingResource implements IdlingResource {
private static final String TAG = "CountingIdlingResource";
private final String resourceName;
private final AtomicInteger counter = new AtomicInteger(0);
private final boolean debugCounting;
// written from main thread, read from any thread.
private volatile ResourceCallback resourceCallback;
// read/written from any thread - used for debugging messages.
private volatile long becameBusyAt = 0;
private volatile long becameIdleAt = 0;
/**
 * Creates a CountingIdlingResource without debug tracing.
 *
 * @param resourceName the resource name this resource should report to Espresso.
 */
public CountingIdlingResource(String resourceName) {
  this(resourceName, false);
}
/**
 * Creates a CountingIdlingResource.
 *
 * @param resourceName the resource name this resource should report to Espresso.
 * @param debugCounting if true increment & decrement calls will print trace information to logs.
 */
public CountingIdlingResource(String resourceName, boolean   debugCounting) {
  this.resourceName = checkNotNull(resourceName);
  this.debugCounting = debugCounting;
}
@Override
public String getName() {
  return resourceName;
}
@Override
public boolean isIdleNow() {
  return counter.get() == 0;
}
@Override
public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
  this.resourceCallback = resourceCallback;
}
/**
 * Increments the count of in-flight transactions to the resource being monitored.
 *
 * This method can be called from any thread.
 */
public void increment() {
  int counterVal = counter.getAndIncrement();
  if (0 == counterVal) {
    becameBusyAt = SystemClock.uptimeMillis();
  }
  if (debugCounting) {
    Log.i(TAG, "Resource: " + resourceName + " in-use-count incremented to: " + (counterVal + 1));
  }
}
/**
 *   Decrements the count of in-flight transactions to the resource being monitored.
 *
 * If this operation results in the counter falling below 0 - an exception is raised.
 *
 * @throws IllegalStateException if the counter is below 0.
 */
public void decrement() {
  int counterVal = counter.decrementAndGet();
  if (counterVal == 0) {
    // we've gone from non-zero to zero. That means we're idle now! Tell espresso.
    if (null != resourceCallback) {
      resourceCallback.onTransitionToIdle();
    }
    becameIdleAt = SystemClock.uptimeMillis();
  }
  if (debugCounting) {
    if (counterVal == 0) {
      Log.i(TAG, "Resource: " + resourceName + " went idle! (Time spent not idle: " +
        (becameIdleAt - becameBusyAt) + ")");
    } else {
      Log.i(TAG, "Resource: " + resourceName + " in-use-count decremented to: " + counterVal);
    }
  }
  checkState(counterVal > -1, "Counter has been corrupted!");
}
/**
 * Prints the current state of this resource to the logcat at info level.
 */
public void dumpStateToLogs() {
  StringBuilder message = new StringBuilder("Resource: ")
      .append(resourceName)
      .append(" inflight transaction count: ")
      .append(counter.get());
  if (0 == becameBusyAt) {
    Log.i(TAG, message.append(" and has never been busy!").toString());
  } else {
    message.append(" and was last busy at: ")
        .append(becameBusyAt);
    if (0 == becameIdleAt) {
      Log.w(TAG, message.append(" AND NEVER WENT IDLE!").toString());
    } else {
      message.append(" and last went idle at: ")
          .append(becameIdleAt);
      Log.i(TAG, message.toString());
      }
    }
  }
}

在耗時線程中調(diào)用

//耗時操作開始調(diào)用
helloWorldServerIdlingResource.increment();
{  
      //做一些耗時操作
}
//結(jié)束后調(diào)用
helloWorldServerIdlingResource.decrement();

</br>


UI Automator

UI Automator 測試框架提供了一組 API 來構(gòu)建 UI 測試,用于在用戶應(yīng)用和系統(tǒng)應(yīng)用中執(zhí)行交互。利用 UI Automator API,您可以執(zhí)行在測試設(shè)備中打開“設(shè)置”菜單或應(yīng)用啟動器等操作。UI Automator 測試框架非常適合編寫黑盒自動化測試,其中的測試代碼不依賴于目標(biāo)應(yīng)用的內(nèi)部實現(xiàn)詳情。

UI Automator 測試框架的主要功能包括:

  • 用于檢查布局層次結(jié)構(gòu)的查看器。
  • 在目標(biāo)設(shè)備上檢索狀態(tài)信息并執(zhí)行操作的 API。
  • 支持跨應(yīng)用 UI 測試的 API。

要求 Android 4.3(API 級別 18)或更高版本。

UI Automator 查看器

uiautomatorviewer 工具提供了一個方便的 GUI,可以掃描和分析 Android 設(shè)備上當(dāng)前顯示的 UI 組件。您可以使用此工具檢查布局層次結(jié)構(gòu),并查看在設(shè)備前臺顯示的 UI 組件屬性。利用此信息,您可以使用 UI Automator(例如,通過創(chuàng)建與特定可見屬性匹配的 UI 選擇器)創(chuàng)建控制更加精確的測試。

uiautomatorviewer 工具位于 <android-sdk>/tools/ 目錄中。

UI Automator API

  • UiDevice
    :用于在目標(biāo)應(yīng)用運行的設(shè)備上訪問和執(zhí)行操作。您可以調(diào)用其方法來訪問設(shè)備屬性,如當(dāng)前屏幕方向或顯示尺寸,按“返回”、“主屏幕”或“菜單”按鈕等。
  • UiCollection
    :枚舉容器的 UI 元素以便計算子元素個數(shù),或者通過可見的文本或內(nèi)容描述屬性來指代子元素。
  • UiObject
    :表示設(shè)備上可見的 UI 元素。
  • UiScrollable
    :為在可滾動 UI 容器中搜索項目提供支持。
  • UiSelector
    :表示在設(shè)備上查詢一個或多個目標(biāo) UI 元素。
  • Configurator
    :允許您設(shè)置運行 UI Automator 測試所需的關(guān)鍵參數(shù)。
// 初始化 UiDevice
mDevice = UiDevice.getInstance(getInstrumentation());

// 按下home鍵
mDevice.pressHome();

//在當(dāng)前主界面,查找一個叫test的元素
UiObject allAppsButton = mDevice.findObject(new UiSelector().description("test"));

// 找到后點擊它
allAppsButton.click();

更多詳細(xì)的使用會在后面實際使用中講到。

壓力測試 Monkey

Monkey是Android中的一個命令行工具,可以運行在模擬器里或?qū)嶋H設(shè)備中。它向系統(tǒng)發(fā)送偽隨機的用戶事件流(如按鍵輸入、觸摸屏輸入、手勢輸入等),實現(xiàn)對正在開發(fā)的應(yīng)用程序進(jìn)行壓力測試。Monkey測試是一種為了測試軟件的穩(wěn)定性、健壯性的快速有效的方法。

Monkey的特征

  • 測試的對象僅為應(yīng)用程序包,有一定的局限性。
  • Monky測試使用的事件流數(shù)據(jù)流是隨機的,不能進(jìn)行自定義。
  • 可對MonkeyTest的對象,事件數(shù)量,類型,頻率等進(jìn)行設(shè)置。

Monkey使用

adb shell monkey [options] <event-count>

options這個是配置monkey的設(shè)置,例如指定啟動那個包,不指定將會隨機啟動所有程序。event-count這個是讓monkey發(fā)送多少次事件。

adb shell monkey -p com.android.test -v 5000

這就是一個簡單的測試,向com.android.test包對應(yīng)的程序發(fā)送5000次隨機的事件,-p指定了測試的包名,-v指定了發(fā)送的隨機事件次數(shù)。

monkey命令行可用參數(shù)

Monkey停止條件

  • 如果限定了Monkey運行在一個或幾個特定的包上,那么它會監(jiān)測試圖轉(zhuǎn)到其它包的操作,并對其進(jìn)行阻止。
  • 如果應(yīng)用程序崩潰或接收到任何失控異常,Monkey將停止并報錯。
  • 如果應(yīng)用程序產(chǎn)生了應(yīng)用程序不響應(yīng)(ANR)的錯誤,Monkey將會停止并報錯。

以上就是我學(xué)習(xí)了解的一些測試,當(dāng)然Android的自動化測試還有很多其他的框架,個人能力有限,這里我就只記錄了我自己了解的一些方式。詳細(xì)的使用方式,可以查看下面的文章:
Android自動化測試--Local Unit Tests使用
Android自動化測試--Instrumented Unit Tests使用
Android自動化測試--Espresso使用
Android自動化測試--UI Automator使用
Android自動化測試--Monkey使用

如果你覺得有用,請在Github不吝給我一個Star,非常感謝。


寫在最后的話:個人能力有限,歡迎大家在下面吐槽。喜歡的話就為我點一個贊吧。也歡迎 Fork Me On Github 。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,326評論 25 708
  • Instrumentation介紹 Instrumentation是個什么東西? Instrumentation測...
    打不死的小強qz閱讀 7,979評論 2 39
  • 標(biāo)簽(空格分隔): Android 單元測試的好處:Martin Fowler在《重構(gòu)》里面還解釋了為什么單元測試...
    背影殺手不太冷閱讀 6,063評論 3 25
  • 星期二。雨。 郵政包裹后續(xù)-------又找了一次商家客服,還是沒等到答案,晚上氣勢洶洶地找了維權(quán)中心,最后還是再...
    紅蜻蜓閱讀 257評論 0 0
  • 條條道路通羅馬,財務(wù)自由之路也有千萬條,只要達(dá)到“老有所養(yǎng)、病有所醫(yī)、親有所護(hù)”的基礎(chǔ)條件,加上每月收入大于每月支...
    富蘭克劉閱讀 1,481評論 3 29

友情鏈接更多精彩內(nèi)容