使用 Espresso 进行测试 - 第 2 部分(Espresso 和边缘案例)

2018-07-23 08:00飞浪

Espresso 和边缘案例

上一篇文章设置和基础知识介绍了测试和设置环境的基础知识。下一步是测试各种技术来测试各种 android 视图,如 toast、字体、意图、网络调用等。本教程涵盖了使用 espresso 测试的边缘情况(如运行时权限、活动结果等)。

测试 ListView 或 RecyclerView

ListView 或 Spinner 使用AdapterView,在运行时显示来自适配器的数据。因此,与其他视图不同,adapterview不会同时显示所有列表项,这意味着onView不会找到当前未加载的视图。

onData()专门用于在对给定位置执行任何操作之前将所需的列表项置于焦点之中。

      // click on 3rd position
// where the type of data source is string
onData(allOf(is(instanceOf(String.class)))).atPosition(2).perform(click());

// select view with text "item 3"
onData(allOf(is(instanceOf(String.class)), is("item 3"))).perform(click());
    

要执行单击RecyclerView。需要espresso-contrib依赖项,它提供RecyclerView特定的方法,如下所示:

      // verify the visibility of recycler view on screen
onView(withId(R.id.news_frag_recycler_list)).check(matches(isDisplayed()));
// perform click on view at 3rd position in RecyclerView
onView(withId(R.id.news_frag_recycler_list))
  .perform(RecyclerViewActions.actionOnItemAtPosition(3, click()));
    

使用自定义意图启动活动

要启动一个活动, ActivityTestRule实例需要一个 Intent 对象,并且应用ActivityTestRule的重载版本。

      ActivityTestRule(SingleActivityFactory<T> activityFactory, boolean initialTouchMode, boolean launchActivity)
    

launchActivity为false表示测试不会自动启动该活动。请参阅实现:

      @Rule
public ActivityTestRule<MainActivity> activityTestRule =
  new ActivityTestRule<MainActivity>(MainActivity.class,true,false /*lazy launch activity*/){
    @Override
    protected Intent getActivityIntent() {
      /*added predefined intent data*/
      Intent intent = new Intent();
      intent.putExtra("key","value");
      return intent;
    }
  };

@Test
public void customizeIntent(){
    // note instead of null, an intent object can be passed
    activityTestRule.launchActivity(null);
}
    

处理 Marshmallow 运行时权限模型

为了增强数据和关键资源的安全性,Android Marshmallow 及更高版本会在应用程序运行时告知用户应用程序使用的资源。其中一些资源将是存储、联系人、位置和其他硬件资源,如传感器、摄像头等。

因此,为了进行测试,需要首先授予运行时权限,最好在使用@Before注释的方法中授予:

      @Before
public void grantPhonePermission() {
  // In M+, trying to call a number will trigger a runtime dialog. Make sure
  // the permission is granted before running this test.
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    getInstrumentation().getUiAutomation().executeShellCommand(
      "pm grant " + getTargetContext().getPackageName()
      + " android.permission.READ_EXTERNAL_STORAGE");
  }
}
    

使用上下文进行初始化

通常在使用库之前需要一个上下文实例来设置库,因此可以从InstrumentationRegistry实例中检索上下文实例。

      @Rule
public ActivityTestRule<NewsActivity> activityTestRule =
        new ActivityTestRule<>(NewsActivity.class);


@Before
public void init(){
    Context context = InstrumentationRegistry.getTargetContext();
    SomeLib.initialize(context); // like fresco, firebase etc
}
    

测试 Toast 可见性

Toast 是浮动消息,在当前屏幕上向用户显示。要自定义 Toast 的外观,请使用以下代码并仔细注意导入语句。

      package com.pavneet_singh;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.closeSoftKeyboard;
import static android.support.test.espresso.action.ViewActions.typeText;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withHint;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import android.support.test.espresso.contrib.RecyclerViewActions;
import android.support.test.filters.LargeTest;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import com.pavneet_singh.espressotestingdemo.R;

@RunWith(AndroidJUnit4.class)
public class MainActivityTest {
  // To launch the mentioned activity under testing
  @Rule
  public ActivityTestRule<MainActivity> mActivityRule =
      new ActivityTestRule<>(MainActivity.class);

  @Test
  public void testButtonClick() {
    // enter name
    onView(withId(R.id.editTextName)).perform(typeText("Pavneet"), closeSoftKeyboard());
    // clear text
    onView(withText("Clear")).perform(click());

    // check hint visibility after the text is cleared
    onView(withId(R.id.editTextName)).check(matches(withHint("Enter Name")));
    onView(withId(R.id.editTextName)).check(matches(isDisplayed()));
    onView(withId(R.id.editTextName))
      .perform(RecyclerViewActions.actionOnItemAtPosition(3, click()));
    // check toast visibility
    onView(withText("Pavneet Toast")).
      inRoot(withDecorView(not(is(activity.getWindow().getDecorView())))).
      check(matches(isDisplayed()));
    }
}
    

检查 ScrollView 中的视图可见性

ScrollView 可以垂直或水平地承载多个视图(EditText、Buttons、CheckBox 等),因此某些视图可能当前在屏幕上不可见。因此,无法对不可见的视图应用matches(isDisplayed()) 。

要测试当前窗口上不可见的视图,请使用withEffectiveVisibility:

      // check view exists in current layout hierarchy
onView(withId(R.id.view_id)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.VISIBLE)));
    

使用 Espresso 测试片段

您的活动(屏幕)可以划分为称为片段的较小容器。在测试期间,您需要先将片段添加到主机活动中,这可以在开始实际测试之前完成,也可以在需要时完成。

      @Rule
public ActivityTestRule<NewsActivity> activityTestRule =
  new ActivityTestRule<>(NewsActivity.class);

@Before
public void yourSetUPFragment() {
  activityTestRule.getActivity()
    .getFragmentManager().beginTransaction();
}
    

此处@Before函数将在开始测试之前执行。它类似于将片段加载到主机活动的设置函数,这在测试仅面向片段时很合适。

此交易也可在稍后需要时进行。

注意:您还可以创建普通片段对象并使用片段事务将它们添加到主机活动。

创建自定义 ViewAction

常见的做法之一是更改TextView中的文本,但在EditText中输入文本的常规方法不能应用于TextView,因此不能使用下面的方法。

      onView(withId(R.id.editText)).perform(typeText("my text"), closeSoftKeyboard());
    

上述操作会失败,因为TextView不支持输入法编辑器 (IME) ,也就是说用户无法通过键盘将值输入到TextView中。要将文本输入到TextView中,需要自定义ViewAction 。

使用自定义 ViewAction 在 TextView 中输入文本

ViewAction是一个抽象类,因此要创建自定义的ViewAction,需要创建一个ViewAction类型的匿名类:

      public static ViewAction setTextInTextView(final String value){
  return new ViewAction() {
    @SuppressWarnings("unchecked")
    @Override
    public Matcher<View> getConstraints() {
      return allOf(isDisplayed(), isAssignableFrom(TextView.class));
//                                            ^^^^^^^^^^^^^^^^^^^
// To check that the found view is TextView or it's subclass like EditText
// so it will work for TextView and it's descendants  
    }

    @Override
    public void perform(UiController uiController, View view) {
        ((TextView) view).setText(value);
    }

    @Override
    public String getDescription() {
        return "replace text";
    }
  };
}
    

然后它可以应用为:

      onView(withId(R.id.textview_id))
  .perform(setTextInTextView("text input"));
    

检查 FontSize 等属性

<div class="rich-text-editor text
曝光31364浏览1622