第一次使用 Espresso Test Recorder 就上手

第一次使用 Espresso Test Recorder 就上手

前言

在 2016 Google I/O 有介紹 Android Studio 2.2 上最新的功能,這些功能可以幫助開發者更快速、便捷地開發程式跟撰寫測試,其中這套 Espresso Test Recorder 工具,可以幫助我們錄下一連串的操作動作,自動生成可執行的 Espresso 測試腳本,並且播放測試腳本在你指定的裝置上。

現在就讓我們來嘗試使用 Espresso Test Recorder  以完成一個測試!

前置準備

  1. 安裝 Android Studio 2.2 或以上的版本
  2. 在專案宣告 Espresso 專屬的 dependencies
androidTestCompile 'com.android.support.test:runner:0.5'
androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2

錄製測試

  1. 開啟你的專案(必須要有 UI 元件可以操作,可參考 googlecodelabs/android-testing
  2. 於工具列開啟 Run → Recored Espresso Test
    %e8%9e%a2%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-13-%e4%b8%8b%e5%8d%885-18-59
  3. 選擇要運行測試程式的裝置(此部分可以選擇實體 device 或 emulator)
  4. Android Studio 會出現 Recored Your Test 視窗

    註:在這之前,可能會出現 Waiting For Debugger 的視窗,此時不要關閉此視窗,它會自行關閉

    在此 Recored Your Test 視窗,會顯示所有你在 app 內的操作(如:打字、點擊、滑動…)
    %e8%9e%a2%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-13-%e4%b8%8b%e5%8d%886-17-02 %e8%9e%a2%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-13-%e4%b8%8b%e5%8d%886-17-14

  5. 在我們做了一些測試步驟後,想要驗證結果是否符合預期,可以點選 Add Assertion
    此時會出現一個截圖視窗,可以讓我們選取欲驗證的元件,編輯、輸入預期結果。
    目前 Add Assertion 只能支援陽春的驗證行為,如:比對字串、是否存在,若想要進階一點的驗證條件,得自己寫。
    %e8%9e%a2%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-13-%e4%b8%8b%e5%8d%886-18-15
  6. 最後當測試得差不多後,選擇 Complete Recording
  7. 填入測試案例的名稱,如:OpenDrawerTest,將會生成一個 java 檔,裡面會自動生成測試腳本,同時也將需要引入的 class 都自動帶入了!是不是很神奇~
    %e8%9e%a2%e5%b9%95%e5%bf%ab%e7%85%a7-2016-12-13-%e4%b8%8b%e5%8d%886-19-56

    package com.example.android.testing.notes.notes;
    
    import android.support.test.espresso.ViewInteraction;
    import android.support.test.rule.ActivityTestRule;
    import android.support.test.runner.AndroidJUnit4;
    import android.test.suitebuilder.annotation.LargeTest;
    import android.view.View;
    import android.view.ViewGroup;
    import android.view.ViewParent;
    
    import com.example.android.testing.notes.R;
    
    import org.hamcrest.Description;
    import org.hamcrest.Matcher;
    import org.hamcrest.TypeSafeMatcher;
    import org.junit.Rule;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    
    import static android.support.test.espresso.Espresso.onView;
    import static android.support.test.espresso.action.ViewActions.click;
    import static android.support.test.espresso.assertion.ViewAssertions.matches;
    import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
    import static android.support.test.espresso.matcher.ViewMatchers.withContentDescription;
    import static android.support.test.espresso.matcher.ViewMatchers.withId;
    import static android.support.test.espresso.matcher.ViewMatchers.withParent;
    import static android.support.test.espresso.matcher.ViewMatchers.withText;
    import static org.hamcrest.Matchers.allOf;
    
    @LargeTest
    @RunWith(AndroidJUnit4.class)
    public class OpenDrawerTest {
    
      @Rule
      public ActivityTestRule<NotesActivity> mActivityTestRule = new ActivityTestRule<>(NotesActivity.class);
    
      @Test
      public void openDrawerTest() {
        ViewInteraction imageButton = onView(
            allOf(withContentDescription("Navigate up"),
                withParent(withId(R.id.toolbar)),
                isDisplayed()));
        imageButton.perform(click());
    
        ViewInteraction textView = onView(
            allOf(withText("Notes"),
                childAtPosition(
                    childAtPosition(
                        withId(R.id.navigation_header_container),
                        0),
                    1),
                isDisplayed()));
        textView.check(matches(withText("Notes")));
    
      }
    
      private static Matcher<View> childAtPosition(
          final Matcher<View> parentMatcher, final int position) {
    
        return new TypeSafeMatcher<View>() {
          @Override
          public void describeTo(Description description) {
            description.appendText("Child at position " + position + " in parent ");
            parentMatcher.describeTo(description);
          }
    
          @Override
          public boolean matchesSafely(View view) {
            ViewParent parent = view.getParent();
            return parent instanceof ViewGroup &amp;&amp; parentMatcher.matches(parent)
                &amp;&amp; view.equals(((ViewGroup) parent).getChildAt(position));
          }
        };
      }
    }
    
    

Espresso Test Recorder 的限制

  1. 目前 Espresso Test Recorder 在  Android Studio 2.2 內仍是 Beta 版,不是很穩定
  2. 只能操控該裝置的那支 app,無法跟系統設定、其他 app 相互動(因為 Espresso 的特性也是如此)
  3. 不能實作等待、過場動作
    若是有一些動畫過場、比較花費時間才能完成的動作,中間的等待時間,必須要自己處理
  4. 僅支援單一、陽春類型的 Assertion
    若要同時驗證同一元件的 2 種屬性, Espresso Test Recorder 只能擇其一使用,想要驗證多種屬性、多個元件,必須自己撰寫
  5. 實際錄製 鍵盤打字 場景會非常不順,會頓頓卡卡的,不宜打字打太多(模擬器/實機 都會卡)
  6. 錄製出來的 Script 常含有多餘的 Matcher,需要額外花一些心神重構 Test Script

結論

Espresso Test Recorder 對於撰寫 Espresso 測試的初學者的確是一套蠻便利的利器,在沒有嘗試寫過完整的測試前,可以試著使用看看。
藉由觀察自動產生出來的測試腳本,可以得知一個完整的測試腳本該具備哪些元素,進而模仿。
然而,Espresso Test Recorder 上述的限制會減少測試腳本的可行性、侷限測試的完整性,使得不一定能夠成功執行自動生成的測試腳本,且 Assertion 也不夠完整。

不過,對於初學者還是蠻鼓勵使用看看 Espresso Test Recorder,或許它無法幫助錄製生成穩定的測試案例,但它帶給我們的最大效益,也許是加快學習撰寫 Espresso 測試腳本的過程。

綜觀以上,雖 Espresso Test Recorder 不是非常完美的工具,但仍可以善用它的優點,使用在簡易的操作與檢驗。如:重複登入帳號 100 次(記得避免使用打字輸入帳密)、重複播放/暫停歌曲 100 次… 等等。
若偶有臨時簡單的測試場景需要撰寫,也可以利用 Espresso Test Recorder 錄製一些臨時的 Script 應付一些臨時的需求,何樂而不為!:)

參考文件

  1. [Youtube] What’s new in Android development tools – Google I/O 2016 (Espresso Test Recorder)
  2. [Android Studio] Create UI Tests with Espresso Test Recorder
廣告

2 thoughts on “第一次使用 Espresso Test Recorder 就上手

發表迴響

Please log in using one of these methods to post your comment:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s