Android自動化測試入門(三)Espresso

Espresso是谷歌力推的一個UI自動化測試框架,新建一個Andrdoid工程的時候默認就引入了Espresso的核心依賴,所以作爲Android開發者,非常有必要學習這個框架。

之前使用UI Automator的時候,我們經常在不同的指令之間添加一個時間延時保證手機端執行完成,在Espresso直接使用onView(),onView()會等待界面執行完在執行下一步。

Espresso和UI Automator一樣,也是在項目的app/src/androidTest文件夾下編寫測試代碼

下面先來個開胃菜

測試TextView是否已經顯示

新建一個測試類HelloWorldTest

@RunWith(AndroidJUnit4.class)
public class HelloWorldTest {

    @Rule
    public ActivityTestRule<MainActivity> activityRule =
            new ActivityTestRule<>(MainActivity.class);

    @Test
    public void listGoesOverTheFold() {
        onView(withText("Hello World!")).check(matches(isDisplayed()));
    }

}

HelloWorldTest非常簡單,就是判斷"Hello World!"是不是已經顯示在屏幕上

  1. 通過@Rule註解來規定測試的Activity是MainActivity,並且測試完後關閉它
  2. 通過withText方法找到屏幕上的對應的控件
  3. onView方法可以等待尋找完成之後再工作。尋找完成之後,調用check方法來判斷該控件是不是已經顯示在屏幕上
  4. 點擊類旁邊的 run Test按鈕 測試完成

Espresso 的API組成

  • onView()onData()是Espresso和視圖交互的入口都是用來定位一個View,onView()是定位普通Vew,onData()可以定位ListVew、GridView 中的一個條目
  • ViewMatchers: 實現了Matcher<? super View>接口,是onView()的參數,將它傳到onView()中幫助定位一個View。
  • ViewActions:對一個View的動作,比如點擊click(),可以傳遞給ViewInteraction.perform()方法
  • ViewAssertions:用來斷言測試結果是否正確,可以傳遞給ViewInteraction.check()方法。

測試點擊界面上的一個按鈕

創建一個clickTest()測試方法,點擊界面上的某個按鈕

 @Test
 public void clickTest(){
        onView(withId(R.id.btn_click))
                .perform(click())
                .check(matches(isDisplayed()));
    }

一般情況下使用withId方法通過View的id來找到一個View,不過有時候可能不同界面中使用了相同的id,這時候可以多個條件一起使用來定位一個View,比如下面

    //id爲R.id.my_view text爲"Hello!"的控件
    onView(allOf(withId(R.id.my_view), withText("Hello!")));
    //既有text "7" 又有text "item: 0"的控件
    onView(allOf(withText("7"), hasSibling(withText("item: 0"))))
        .perform(click());

點擊按鈕,將TextView的文字由Hello World!改爲Hello Espresso!,並檢查結果。

app中的代碼

final TextView textView = findViewById(R.id.tv_text);
findViewById(R.id.btn_click).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                textView.setText("Hello Espresso");
            }
        });

測試類中添加新的測試方法

 @Test
    public void matchText(){
        //點擊按鈕,將Hello World!改爲Hello Espresso!
        onView(withId(R.id.btn_click)).perform(click());
        //檢查TextView上的文字是不是Hello Espresso!
        onView(withId(R.id.tv_text)).check(matches(withText("Hello Espresso!")));
    }

測試RecyclerView

測試RecyclerView,跳到RecyclerView的某一位置處執行點擊

首先需要引入一個依賴espresso-contrib這裏麪包含包含 DatePicker、RecyclerView 和 Drawer 操作、無障礙功能檢查以及 CountingIdlingResource

 androidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0-alpha04'

首先在應用的app中新建一個Activty來顯示一個列表,接着前面的例子,在MainActivity中定義一個按鈕點擊進入列表頁。

findViewById(R.id.btn_to_recyclerview).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivity(new Intent(getApplicationContext(),RecyclerViewActivity.class));
            }
        });
測試根據position定位到某一條

新建一個RecyclerView的測試類RecyclerViewTest,測試根據position定位到某一條

@RunWith(AndroidJUnit4.class)
public class RecyclerViewTest {

    @Rule
    public ActivityTestRule<MainActivity> activityRule =
            new ActivityTestRule<>(MainActivity.class);

    @Test
    public void testRecyclerView(){
        //點擊進入列表頁
        onView(ViewMatchers.withId(R.id.btn_to_recyclerview)).perform(click());
        //定位到第30條並點擊
        onView(ViewMatchers.withId(R.id.recyclerview))
                .perform(RecyclerViewActions.actionOnItemAtPosition(30, click()));
        //判斷第30條的文字"item++++++++++++++30"是否顯示了
        onView(ViewMatchers.withText("item++++++++++++++30")).check(matches(isDisplayed()));
    }
}
自定義匹配器來查找一個Item

還可以通過自定義匹配器來匹配一個Item是否存在,匹配器如下

   public static Matcher<MyAdapter.ViewHolder> withAdaptedData(){
         return new TypeSafeMatcher<MyAdapter.ViewHolder>(){

             @Override
             public void describeTo(Description description) {
                 description.appendText("測試item");
             }

             @Override
             protected boolean matchesSafely(MyAdapter.ViewHolder item) {
                 return item.mTextView.getText().equals("item++++++++++++++30");
             }
         };
    }

通過自定義匹配器滑動到RecyclerView的相應的位置,並判斷該條item是否顯示

  @Test
    public void matchItemData(){
        //點擊進入列表頁
        onView(ViewMatchers.withId(R.id.btn_to_recyclerview)).perform(click());
        //根據匹配器滑動到相應的位置
        onView(withId(R.id.recyclerview)).perform(RecyclerViewActions.scrollToHolder(withAdaptedData()));
        //判斷期望的文本有沒有顯示
        onView(withText("item++++++++++++++30")).check(matches(isDisplayed()));
    }

Espresso-Intents

Espresso-Intents是Espresso的一個擴展,支持對被測應用發出的intent 進行驗證和打樁,適用於 Android Intent,可以判斷Intent是否跳轉成功,傳遞的參數是否正確。

使用它需要添加新的依賴

androidTestImplementation 'androidx.test.espresso:espresso-intents:3.1.0'

編寫Espresso-Intents之前,首先需要設置IntentsTestRule,ntentsTestRule 會在帶有 @Test 註解的每個測試運行前初始化 Espresso-Intents,並在每個測試運行後釋放 Espresso-Intents。

@Rule
public IntentsTestRule<MainActivity> intentsTestRule =
            new IntentsTestRule<>(MainActivity.class);

Espresso中可以使用 intended()intending()這兩個方法來驗證一個Intent。

測試去一個界面選號碼然後打電話的動作

app中的步驟是這樣的,點擊“獲取號碼”按鈕,通過startActivityForResult去聯繫人頁面選擇一個號碼返回保存到成員變量中,然後點擊“打電話”按鈕通過Intent打開系統打電話的界面。下面是測試代碼:

//測試去聯繫人頁面獲取一個電話號碼
@Test
public void testPickIntent(){
     //創建一個Intent用來封裝返回的數據
     Intent intent = new Intent();
     intent.putExtra(ContactsActivity.KEY_PHONE_NUMBER,"123456789");
     //驗證如果有相應的startActivityOnResult發生,就返回前面自己創建的結果。
     intending(IntentMatchers.hasComponent(ComponentNameMatchers.hasShortClassName(".ContactsActivity")))
         .respondWith(new Instrumentation.ActivityResult(Activity.RESULT_OK,intent));
     //點擊去聯繫人頁面的按鈕
     onView(withId(R.id.btn_to_contacts)).perform(click());
     //點擊打電話的按鈕
     onView(withId(R.id.btn_call)).perform(click());
     //驗證打電話的Intent是否發送
     intended(allOf(
             hasAction(Intent.ACTION_CALL),
             hasData("tel:123456789")
     ));
}
  • 上面的測試中,intending() 方法用來測試startActivityOnResult,當startActivityOnResult動作發生的時候,就返回自己創建的一個Intent
  • intended() 方法用來測試startActivity打開打電話頁面的時候Intent的參數是否正確。
測試打開系統相機獲取圖片的動作

動作:一個Button,一個ImageView,點擊按鈕拍照,拍照確定後將圖片顯示到ImageView上面

   @Test
    public void testTackPhoto() throws InterruptedException {
         //自定義一個拍照返回drawable圖片的Intent
         Intent picIntent = new Intent();
         //把drawable放到bundle中傳遞
        Bundle bundle = new Bundle();
        Bitmap bitmap = BitmapFactory.decodeResource(intentsTestRule.getActivity().getResources(), R.mipmap.ic_launcher);
        bundle.putParcelable("data", bitmap);
        picIntent.putExtras(bundle);
        Instrumentation.ActivityResult result =
                new Instrumentation.ActivityResult(Activity.RESULT_OK,picIntent);
        //判斷是否有包含ACTION_IMAGE_CAPTURE的Intent出現,出現就給它返回result
        intending(hasAction(MediaStore.ACTION_IMAGE_CAPTURE)).respondWith(result);

        //判斷ImageView上沒有顯示drawable
        onView(withId(R.id.iv_take_photo)).check(matches(not(hasDrawable())));
        //點擊拍照按鈕去拍照界面
        onView(withId(R.id.btn_take_photo)).perform(click());
        //判斷ImageView上顯示了一個drawable
        onView(withId(R.id.iv_take_photo)).check(matches(hasDrawable()));
    }

    private BoundedMatcher<View, ImageView> hasDrawable(){
         return new BoundedMatcher<View, ImageView>(ImageView.class) {
             @Override
             protected boolean matchesSafely(ImageView item) {
                 return item.getDrawable()!=null;
             }

             @Override
             public void describeTo(Description description) {
                 description.appendText("是否有drawable");
             }
         };
    }
  • 自定義一個BoundedMatcher,用來判斷一個ImageView上面是否有圖片
  • 從資源文件中找一張圖片,自定義一個Bitmap返回給帶有ACTION_IMAGE_CAPTURE這個action的Intent
  • 點擊拍照按鈕之前和之後都檢查一下ImageView上面是否有圖片。拍照之前沒有,拍照之後有則測試通過。

Espresso Web

Espresso Web用來測試Android中的WebView,混合應用,可以測試js和原生的交互

使用前首先引入依賴包

androidTestImplementation 'androidx.test.espresso:espresso-web:3.2.0'

下面的例子中,使用官網Demo中的本地Html頁面測試,裏面有javaScript相關的操作。完整demo看文末鏈接

簡單使用:

 @Test
    public void testWebViewHasDisplay(){
        //點擊進入WebViewActivity
        onView(withId(R.id.btn_go_to_web)).perform(click());
        //判斷頁面的標題是否顯示了
        onWebView().check(webMatches(Atoms.getTitle(),containsString("Hello Espresso Web")));
    }

測試JavaScript,爲了支持 JavaScript 求值,被測 WebView 必須啓用 JavaScript。通過forceJavascriptEnabled()方法來開啓

 @Rule
    public ActivityTestRule<WebViewActivity> activityWebRule =
            new ActivityTestRule<WebViewActivity>(WebViewActivity.class,false,false){
                @Override
                protected void afterActivityLaunched() {
                    onWebView().forceJavascriptEnabled();
                }
        };

測試:在輸入框輸入文字,點擊某個按鈕,將輸入框中的文字,賦值給另一個標籤。檢測該標籤上的文字是不是輸入的文字。

    @Test
    public void testJsInteraction(){
        //點擊進入WebViewActivity
        onView(withId(R.id.btn_go_to_web)).perform(click());
        onWebView()
                //html界面上找到id爲text_input的元素
                .withElement(findElement(Locator.ID, "text_input"))
                //清除之前的文字
                .perform(DriverAtoms.clearElement())
                //輸入文字Lily
                .perform(DriverAtoms.webKeys("Lily"))
                //通過id找到切換文字的按鈕
                .withElement(findElement(Locator.ID, "changeTextBtn"))
                //點擊按鈕
                .perform(webClick())
                //通過id找到顯示文字的標籤
                .withElement(findElement(Locator.ID, "message"))
                //判斷標籤上的文字是不是Lily
                .check(webMatches(getText(), containsString("Lily")));
    }

測試:在輸入框中輸入文字,點擊提交按鈕提交表單,檢測表單裏的值跟輸入的文字是否一樣

@Test
    public void testJsFormSubmit(){
        //點擊進入WebViewActivity
        onView(withId(R.id.btn_go_to_web)).perform(click());
        onWebView()
                // html界面上找到id爲text_input的元素
                .withElement(findElement(Locator.ID, "text_input"))
                // 清除之前的文字
                .perform(clearElement())
                // 輸入文字Lily
                .perform(DriverAtoms.webKeys("Lily"))
                // 通過id找到提交按鈕
                .withElement(findElement(Locator.ID, "submitBtn"))
                // 點擊按鈕執行提交動作
                .perform(webClick())
                // 通過id找到form表單的id
                .withElement(findElement(Locator.ID, "response"))
                // 驗證提交的內容是不是Lily
                .check(webMatches(getText(), containsString("Lily")));
    }

Espresso常用的功能就練習完了,還有一些不常用的功能可以點擊去官網查看

點擊查看完整demo地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章