Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

Espresso Idling Resource

$
0
0


前沿

上一篇进行Espresso 安装方面介绍,也就是测试环境的设置。下面几篇会对照官方文档进行一个分析。避免与主题脱节。关于Espresso文章不分先后。

1.翻译官网文档

Espresso的核心是它能够无缝地同步所有测试操作与正在测试的应用程序。默认情况下,Espresso会等待当前消息队列中的UI事件被处理,并在默认的AsyncTasks完成之后再进入下一个测试操作。
然而,存在应用程序通过非标准手段执行后台操作(例如与web服务通信)的情况; 例如:直接创建和管理线程。在这种情况下,你必须使用Idling Resource来通知应用程序的长时间运行操作。
我认为,实现Idling Resource结构重写判断条件isIdleNow()可以实现模拟的异步操作

1.1创建和注册Idling资源

您可以实现IdlingResource接口自己或使用一个已经存在的实现,如CountingIdlingResource,包含在espresso-idling-resource包中。
此接口需要暴露于测试或在其中创建并注入应用程序。然后,通过调用注册您的Idling资源(S)与espresso中的一个或多个Espresso.registerIdlingResource()在测试设置。
Sample: https://github.com/googlesamples/android-testing/tree/master/ui/espresso/IdlingResourceSample

1.2 Idling Resource approaches

Counting running jobs:当一个作业启动,自动增加的计数器。当它完成时,递减它。如果计数器为零,应用程序将处于空闲状态。这种方法非常简单,解释了大多数情况。CountingIdlingResource原理正是这一点。
Querying state:这可能是更可靠的问一个工作队列或HTTP客户端(或任何正在做工作背景),如果它是忙。如果状态是暴露的,实现Idling Resource 是很容易的。
请注意,Idling Resource implementations for okhttp怠速资源。

Blog: https://github.com/JakeWharton/okhttp-idling-resource

1.3 IdlingResource 接口

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.support.test.espresso;

/**
 * Represents a resource of an application under test which can cause asynchronous background work
 * to happen during test execution (e.g. an intent service that processes a button click). By
 * default, {@link Espresso} synchronizes all view operations with the UI thread as well as
 * AsyncTasks; however, it has no way of doing so with "hand-made" resources. In such cases, test
 * authors can register the custom resource and {@link Espresso} will wait for the resource to
 * become idle prior to executing a view operation.
 * <br><br>
 * <b>Important Note:</b> it is assumed that the resource stays idle most of the time.
 */
public interface IdlingResource {

  /**
   * Returns the name of the resources (used for logging and idempotency  of registration).
   */
  public String getName();

  /**
   * Returns {@code true} if resource is currently idle. Espresso will <b>always</b> call this
   * method from the main thread, therefore it should be non-blocking and return immediately.
   */
  public boolean isIdleNow();

  /**
   * Registers the given {@link ResourceCallback} with the resource. Espresso will call this method:
   * <ul>
   * <li>with its implementation of {@link ResourceCallback} so it can be notified asynchronously
   * that your resource is idle
   * <li>from the main thread, but you are free to execute the callback's onTransitionToIdle from
   * any thread
   * <li>once (when it is initially given a reference to your IdlingResource)
   * </ul>
   * <br>
   * You only need to call this upon transition from busy to idle - if the resource is already idle
   * when the method is called invoking the call back is optional and has no significant impact.
   */
  public void registerIdleTransitionCallback(ResourceCallback callback);

  /**
   * Registered by an {@link IdlingResource} to notify Espresso of a transition to idle.
   */
  public interface ResourceCallback {
    /**
     * Called when the resource goes from busy to idle.
     */
    public void onTransitionToIdle();
  }
}

2.Hamcrest框架的引入

使用Esprosso框架进行自动化脚本编写的同事引入Hamcrest框架可以做到事半功倍。

hamcrest 详细介绍

Hamcrest是一个书写匹配器对象时允许直接定义匹配规则的框架.有大量的匹配器是侵入式的,例如UI验证或者数据过滤,但是匹配对象在书写灵活的测试是最常用

Hamcrest从一开始就设计结合不同的单元测试框架.例如,Hamcrest可以使用JUnit3和4和TestNG.(详细查看完整Hamcrest发布中的例子).在一个现有的测试套件中迁移到使用Hamcrest风格的断言是很容易的,因为其他断言风格可以和Hamcrest的共存.

通过使用从mock Objects框架概念匹配器到Hamcrest匹配器桥接适配器Hamcrest也可以在mock objects框架中.例如,JMock 1系统规定是Hamcrest风格.Hamcrest提供了一个JMock 1适配器来允许你在JMock 1测试中使用Hamcrest匹配器.JMock 2不需要这样一个适配器层,因为它被设计使用Hamcrest作为一个匹配库.Hamcrest还提供了EasyMock 2适配器.同样,详细查看Hamcrest例子.

Hamcrest带有一个有用的匹配器库.以下是一些最重要的.

核心

    anything - 总是匹配,如果你不关心测试下的对象是什么是有用的
    describedAs - 添加一个定制的失败表述装饰器
    is - 改进可读性装饰器 - 见下 “Sugar”
    逻辑
    allOf - 如果所有匹配器都匹配才匹配, short circuits (很难懂的一个词,意译是短路,感觉不对,就没有翻译)(像 Java &&)
    anyOf - 如果任何匹配器匹配就匹配, short circuits (像 Java ||)
    not - 如果包装的匹配器不匹配器时匹配,反之亦然

对象

    equalTo - 测试对象相等使用Object.equals方法
    hasToString - 测试Object.toString方法
    instanceOf, isCompatibleType - 测试类型
    notNullValue, nullValue - 测试null
    sameInstance - 测试对象实例
    Beans
    hasProperty - 测试JavaBeans属性

集合

    array - 测试一个数组元素test an array’s elements against an array of matchers
    hasEntry, hasKey, hasValue - 测试一个Map包含一个实体,键或者值
    hasItem, hasItems - 测试一个集合包含一个元素
    hasItemInArray - 测试一个数组包含一个元素

数字

closeTo - 测试浮点值接近给定的值
greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 测试次序
文本
equalToIgnoringCase - 测试字符串相等忽略大小写
equalToIgnoringWhiteSpace - 测试字符串忽略空白
containsString, endsWith, startsWith - 测试字符串匹配

更多详细信息可以查看 工程目录下的 External LIbraries( 前提是你已经引入了Hamcrest库的依赖)

这里写图片描述

3.Record Esprosso Test

在进行自动化测试的开始,首先学习一下Record Esprosso Test,它是官方提供的一个可以方便进行路径录制的工具,当然现在还是有些bug的。可以作为一种快速学习的样板。


1.进入工程

Run—->Record Esprosso Test
–> Select Deplyment Target

这里写图片描述

2.进入需要录制脚本app

这里写图片描述

然后可以操作具体的路径。
这里写图片描述

点击Complete Recording ,并指定生成的路径就给你生成具体的测试脚本。
But,直接运行生成的脚本
这里写图片描述

具体问题,只能呵呵了!!!!,但是我们可以模仿着生成的脚本。进行修改,学习。还是很方便的。

4.单元测试效果

下面是一组简单的测试 有下拉测试,点击测试。断言判断。自定义 Idling Resource

录制视频存放路径 2M
http://pan.baidu.com/s/1nuJ5DUx


5.贴代码进行分析




import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

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.swipeDown;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withParent;
import static org.hamcrest.Matchers.allOf;

/**
 * 1.只是模拟交互,有一个新的需求模拟数据的改变,对Ui的改变
 * 2.测试报告生成
 * 3.做黑盒测试足够了,如果进行白盒测试需要与数据进行交互
 */

/**
 * Created by weichyang on 2017/1/20.
 */
public class MainActivityTest {

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

    //跳转到
    HomeFragment homeFragment;
    InvestmentFragment moneyFgFragment;
    DiscoverFragment discoverFragment;
    BMineFragment mineFragment;
    private static IdlingResource idlingResource;

    // 初始化数据
    @Before
    public void initTesttingData() {


    }

    @After
    public void destroyData() {


    }

    //进入首页,点击闪赚,进入产品页面,下拉刷新,返回首页

    @Test
    public void AenterProductionSz() {

        idlingResource = new ElapsedTimeIdlingResource(DateUtils.SECOND_IN_MILLIS * 4);
        Espresso.registerIdlingResources(idlingResource);

        //检查按钮是否显示
        ViewInteraction radioButton3 = onView(
                Matchers.allOf(ViewMatchers.withId(R.id.main_rb),
                        withParent(withId(R.id.main_tab)),
                        isDisplayed()));
        radioButton3.perform(click());

        Log.d("xxxxx", "-----------------------------start");
        idlingResource = new ElapsedTimeIdlingResource(DateUtils.SECOND_IN_MILLIS * 5);
        Espresso.registerIdlingResources(idlingResource);




        /**
         * 点击第一个入口
         */
        ViewInteraction linearLayout = onView(
                allOf(childAtPosition(
                        withId(R.id.gridview),
                        1),
                        isDisplayed()));
        linearLayout.perform(click());
        Log.d("xxxxx", "-----------------------------end");

        //刷新
        ViewInteraction pullToRefreshListView = onView(allOf(withId(R.id.lstview_production_detail), withParent(withId(R.id.line_production))));
        pullToRefreshListView.perform(swipeDown());
        // 返回首页 关闭
        //测试完毕关闭列表
        ViewInteraction linearLayout2 = onView(
                allOf(withId(R.id.topbarLeftLinearLayout),
                        withParent(allOf(withId(R.id.topbar_layout), withParent(withId(R.id.top_bar)))),
                        isDisplayed()));
        linearLayout2.perform(click());

        // Clean up
        Espresso.unregisterIdlingResources(idlingResource);


    }



    /**
     * 自定义过滤器 position
     *
     * @param parentMatcher
     * @param position
     * @return
     */
    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 && parentMatcher.matches(parent)
                        && view.equals(((ViewGroup) parent).getChildAt(position));
            }
        };
    }

    private static void waitFor(long waitingTime, boolean success) {



    }

}

自定义 ElapsedTimeIdlingResource 根据传入的异步时间。进行资源Idling



import android.support.test.espresso.IdlingResource;

public class ElapsedTimeIdlingResource implements IdlingResource {
    private final long startTime;
    private final long waitingTime;
    private ResourceCallback resourceCallback;

    public ElapsedTimeIdlingResource(long waitingTime) {
        this.startTime = System.currentTimeMillis();
        this.waitingTime = waitingTime;
    }

    @Override
    public String getName() {
        return ElapsedTimeIdlingResource.class.getName() + ":" + waitingTime;
    }

    @Override
    public boolean isIdleNow() {
        long elapsed = System.currentTimeMillis() - startTime;
        boolean idle = (elapsed >= waitingTime);
        if (idle) {
            resourceCallback.onTransitionToIdle();
        }
        return idle;
    }

    @Override
    public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
        this.resourceCallback = resourceCallback;
    }
}

分析:

1.关于屏幕上过滤具体元素,进行操作的方法,这里使用Espresso Recorder 自动生成的脚本方式,这种方式可以找到显示在屏幕上的大部分元素。

     ViewInteraction linearLayout2 = onView(
                allOf(withId(R.id.topbarLeftLinearLayout),
                        withParent(allOf(withId(R.id.topbar_layout), withParent(withId(R.id.top_bar)))),
                        isDisplayed()));

onView()过滤可以看到的Ui组件。匹配器
Allof()是Harmcret 框架提供的脚本,表示在allof()中的条件必须全部符合
withParent(),匹配元素的父元素

ViewInteraction 得到匹配结果是ViewInteraction 对象。通过它可以执行具体ViewAction

2.
ElapsedTimeIdlingResource 类属于自定义 IdlingResource ,根据传入waitingTime。主线程可以停留具体的时间,执行异步操作,

//通用格式
idlingResource = new ElapsedTimeIdlingResource(DateUtils.SECOND_IN_MILLIS * 5);
Espresso.registerIdlingResources(idlingResource);

取消注册

// Clean up
Espresso.unregisterIdlingResources(idlingResource);

对于 isIdleNow()这个函数,需要我们指定具体的判断逻辑,条件符合就会通知主线程,继续下一个步骤

有的blog设置了

//设置异步操作测试超时时间
IdlingPolicies.setMasterPolicyTimeout( waitingTime * 10 TimeUnit.MILLISECONDS);            
IdlingPolicies.setIdlingResourceTimeout(waitingTime * 10, TimeUnit.MILLISECONDS);

上面这设置与否没有任何影响。(想具体了解的可以看 页脚的引用 2 Espresso测试)

3.自定义元素过滤器,过滤具体位置的元素(下面这个是Espresso Recorder 生成的)

   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 && parentMatcher.matches(parent)
                        && view.equals(((ViewGroup) parent).getChildAt(position));
            }
        };
    }

matchesSafely()方法中的条件,需要我们去指定。当符合匹配条件,就会过滤掉ui组件。

6.总结

1.关于Espresso 框架的学习,需要实践才能理解一些api用法

2.具体的写法与具体的业务配和,没有万能的写法。都需要进行简单的调试才可以运行

3. 上面的代码只是提供一个范例。配套demo需要下载官方的进行学习,这里不进行提供


引用

Harmcret 介绍 http://blog.csdn.net/shuangde800/article/details/9109131
Espresso测试 http://www.aichengxu.com/android/2486620.htm

作者:o279642707 发表于2017/1/22 13:13:30 原文链接
阅读:8 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>