(4.5.5.2)Espresso的基础

来源:互联网 发布:python 基础 编辑:程序博客网 时间:2024/06/18 14:48

  • 一创建第一个测试文件
    • 1 文件位置
    • 2 简单事例
  • 二理论知识
    • 1 onView
    • 2 为view执行一个动作
    • 3 断言检查
    • 1附 AdapterView中使用onData ListView GridView
      • 11 onData的简单样例
    • 4 测试与日志
  • 参考问下

一、创建第一个测试文件

1.1 文件位置

好了!我们现在有工具来跑测试了,接下来只要添加我们的测试用例了!

于是问题来了,我们应该在哪里添加测试用例呢?

如果你在Android Studio中新建一个工程,会发现在src目录下,和main平行的还有一个androidTest目录。

一般而言,我们将工程代码放在src/main/java目录下,将与之相关的测试代码放在src/androidTest/java目录下。如下所示:

src/    androidTest/java    ----这里存放instrumentation test相关的代码    main/java           ----这里存放工程代码

同时,为了让工程更容易维护,建议将相应Class的测试代码放到相同名称的包下面,比如,在Package-name下面有一个Class A:

src/main/java/package-name/A.java

那么,建议将A的测试类放到androidTest下面对应的路径下:

src/androidTest/java/package-name/ATest.java

当然实际中的放置方式可能前边万化,随后我们会讲下我们适用的方式;

1.2 简单事例

上面这段测试代码需在代码中创建一个MainActivity,布局中有一个Textview,并且设置了内容“Hello world!”,测试的目的是看MainActivity布局中显示为“Hello World”的TextView是否显示

于是,我应该在androidTest/package-name/module/main下面创建一个MainActivityTest类,键入如下代码:

@RunWith(AndroidJUnit4.class)  @LargeTest  public class HelloWorldEspressoTest {      @Rule      public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class);      @Test      public void listGoesOverTheFold() {          onView(withText("Hello world!")).check(matches(isDisplayed()));      }  }  
  • 运行方法

在文件上右键— Run “xx”

@RunWith(AndroidJUnit4.class)  @LargeTest  ps:这部分描述了如何在JUnit 4 style中创建一个新的Espresso测试类,并用ActivityTestRule来减少所需写的样板代码。通过使用ActivityTestRule,测试框架测试时会在声明了注解@Test@Before的方法之前加载activity。框架控键将会在测试结束和所有声明了@After的方法运行完毕后关闭acitivity。

二、理论知识

Espresso的API鼓励开发者明确地思考用户与应用交互时可能的操作–定位一个UI并且进行交互。同时,该框架禁止直接访问应用中的Activity和View,因为持有该对象并且在非UI线程中进行操作是引起意想不到的情况的主要原因。因此,你将不会在Espresso API中看到像getView和getCurrentActivity这样的方法。你仍可以通过实现你资源的ViewActions和ViewAssertions来安全地对view进行操作。

这里写图片描述

其实上述图基本上已经将所有基本知识一网打尽,离线版本pdf:

  • 定位
    • OnView: 定位View 涉及Matchers
    • OnData: 定位ListView的item 涉及Matchers
  • 动作perform()
    • ViewAction
  • 断言check
    • ViewAsserion 涉及Matchers

其实看到最后就是学习一个 Matchers

来一个简单的示例吧:

onView(withId(R.id.my_view))      // withId(R.id.my_view) is a ViewMatcher    .perform(click())               // click() is a ViewAction    .check(matches(isDisplayed())); // matches(isDisplayed()) is a ViewAssertion  

2.1 onView

在大多数例子中,传入hamcrest匹配器的onView方法用来匹配当前view层级中的一个view。对于熟悉如何在Mockito或Junit中使用Matchers的人Mathers将会很强大。如果你对hamcrest匹配器不熟悉,我们建议你从 this presentation(http://www.slideshare.NET/shaiyallin/hamcrest-matchers)开始学习

通常所描述的view都有一个唯一的R.id,一个简单的withId匹配器将会缩小查找的范围。然而,在很多情况下在开发测试时你并不能确定R.id。例如,指定的view也许没有R.id或者R.id并不唯一。普通的测试指南这时不适用并且很难进行代码编写因为通常的方法(findViewById())来访问view并不起作用。因此,你也许需要访问持有该view的Activity或者Fragment或者查找一个已知id的容器,进而导航到当前这个特殊的view

Espresso通过使用已存在的ViewMatcher或者你自定义的来缩小查找的view范围

  • 用如下的例子来进行view的查找:
onView(withId(R.id.my_view))
  • 有时,R.id的值在多个view间共用。这种情况下使用R.id将会抛出一个AmbiguousViewMatcherException(例如)异常。异常信息提供了一个当前的view层级中代表该view的文本信息,通过这个信息可以查找到与这个不唯一的R.id匹配的所有view。
java.lang.RuntimeException:  com.google.android.apps.common.testing.ui.espresso.AmbiguousViewMatcherException:  This matcher matches multiple views in the hierarchy: (withId: is <123456789>)  ...

浏览view的所有属性,你可以找到唯一识别的属性(在上面的例子中,一个view有text为“Hello!”)。你可以通过结合匹配器来缩小搜索的范围:

onView(allOf(withId(R.id.my_view), withText("Hello!")))  onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))  

注:在性能良好的应用中,所有用户可以进行交互的view需要包含描述性的文本或者有内容描述(详情请查看 Android accessibility guidelines。)如果你通过“withText”或者”withContentDescription”进行onView查找如果不能缩小查找的范围,可以把它当成一个可以访问的bug。

注:用最少的描述匹配器来找到你所查询的view。不要进行过多的描述因为这会强制框架去做更多不必要的工作。例如,如果一个view在文本描述上是唯一的,你不需要再通过TextView的赋值来查找。对于大多数view来说R.id应该就足够了。

注:如果目标view存在于AdapterView(例如ListView,GridView,Spinner)中,onView方法可能会不管用,推荐使用onData方法。

  • 查看 ViewMatchers 来看Espresso所提供的匹配器。
  • 如果要查找控件,在方法onView()中传入目标view的view匹配器。更多详情也可查看Specifying a View Matcher(https://developer.android.com/training/testing/ui-testing/espresso-testing.html#specifying-view-matcher)

2.2 为view执行一个动作

当你为目标view找到一个合适的匹配器时,可以通过perform方法来为这个view执行ViewAction。

  • 例如,点击一个view:
onView(...).perform(click());
  • 你也可以在一次调用中同时执行多个动作:
onView(...).perform(typeText("Hello"), click());
  • 如果你要操作的view位于ScrollView内(纵向或横向),需要考虑到与scrollTo()连用时(如click()和typeText())需要view进行显示。这样保证了在进行其它操作之前该view已经显示。
onView(...).perform(scrollTo(), click());

注:如果view已经显示,那么scrollTo()将不会起作用,在某些情况下如在大分辨率下显示该view时可以安全地使用该方法。(例如,你同时在小屏幕和大屏幕分辨率的手机上同时进行测试)

注:scrollTo()必须在ScrollView内,例如list等会在一定机型上不起作用

看 ViewActions查看Espresso更多的view动作

2.3 断言检查

通过方法check()断言可作用于当前选中的view。使用最多的是matches()断言,它通过ViewMatcher来断言当前选中的view的状态。

例如,检测一个view是否设置文本“Hello!”:

onView(...).check(matches(withText("Hello!")));

注:方法onView中不要包含参数“assertions”–相反,在check块中直接声明你所要检测的内容。例如:
如果你想要断言一个view中设置了”Hello!”,下面是一个不好的示例:

// 不要在onView内使用像withText之类的断言onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));

另一方面,如果你想要断言一个设置“Hello!”的view没被隐藏—例如改变了view的可见不可见的标记—上面的代码是可以的
注:断言一个view没有显示与断言一个view不存在于当前的view层级是不同的

2.1附 AdapterView中使用onData ( ListView , GridView …. )

AdapterView涉及到了复用逻辑,所需要的验证的view可能未被显示

AdapterView是一种特殊的类型,它从Adapter中动态地加载数据。AdapterView最通常的例子就是ListView。与LinerLayout静态widgets相反,只有一部分AdapterView子view会被加载到当前的view层级中,通过onView进行查找时当前未加载的view将不会被找到。Espresso通过onData()预先操作它或它的子类来处理即将加载的adapter的item。

注:初始化时就显示在屏幕上的adapter中的view你也可以不适用onData()因为他们已经被加载了。然而,使用onDta()会更加安全。

提醒:在打破了继承约束(尤其是getItem()的API)实现了AdatpterView的自定义view中onData()是有问题的。在这中情况下,做好的做法就是重构应用的代码。如果不重构代码,你也可以实现自定义的AdapterViewProtocol来实现。查看Espresso的AdapterViewProtocols 来查看更多信息。

2.1.1 onData的简单样例

这个例子演示了如何使用onData():
在SimpleActivity中包含几个条目信息的Spinner—文字代表咖啡饮料的类型。
当选中一个条目时,TextView将显示成”One %s a day!”,%s是选中的条目。
目标是检测打开一个Spinner,选中一个条目,然后确定TextView中包含那个条目。由于Spinner是AdapterView的子类,建议使用onData()而不是onView()来进行条目的匹配。

  • 点击Spinner来打开要选择的条目
onView(withId(R.id.spinner_simple)).perform(click());
  • 点击条目“Americanno”

Spinner将内容以ListView的形式展开—内容可能会非常长,有的不在当前的view层级—通过使用onData()我们强制所需要的view元素加载到当前的view层级。Spinner中的内容类型是String,我们想匹配一个条目是String类型,文字是“Americano”:

onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
  • 检测TextView包含String “Americano”
onView(withId(R.id.spinnertext_simple))  .check(matches(withText(containsString("Americano"))));
  • 这种情况下,请查看指南 Locating a view in an AdapterView (https://developer.android.com/training/testing/ui-testing/espresso-testing.html#locating-adpeterview-view)

其他示例,自己解读下试试:

onData(allOf(is(instanceOf(Map.class)),          hasEntry(equalTo(LongListActivity.ROW_TEXT), is("test input")));  

2.4 测试与日志

Espresso打印了所有view的动作信息。例如:

ViewInteraction: Performing 'single click' action on view with text: Espresso

当测试失败时Espresso提供了有用的debugging信息。

  • 当onView()执行失败时Espresso打印view层级的异常信息。

当onView()找不到目标view时,将会抛出NoMatchingViewException异常。你可以通过查看view层级的异常信息来查看匹配器为什么不能匹配任何view。

如果OnView()查找到了与匹配器匹配的多个view,AmbiguousViewMatcherException异常将会抛出。

view层级信息将会打印并且所有匹配的view将会标上MATCHES标签:

java.lang.RuntimeException:com.google.android.apps.common.testing.ui.espresso.AmbiguousViewMatcherException:This matcher matches multiple views in the hierarchy: (withId: is <123456789>)
  • AdapterView警告

Espresso在出现AdapterView时会提示警告。当onView()查找显示在当前view层级中AdapterView中的view但是却抛出NoMatchingViewException异常时,通常的解决办法就是使用onData().异常信息包含一系列adapter 中view的警告。你可以查看这些信息通过与onData结合来查找目标view。

参考问下

原文地址: https://google.github.io/android-testing-support-library/docs/espresso/index.html

Android测试:https://developer.android.com/training/testing/start/index.html#config-instrumented-tests

0 0
原创粉丝点击