Android Espresso单元测试

Posted xuguoli_beyondboy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Espresso单元测试相关的知识,希望对你有一定的参考价值。

Espresso介绍

android单元测试中,谷歌官方推荐使用Espresso框架,根据谷歌官方介绍,Espresso的最关键的优势就是它能自动同步模拟行为对UI的测试,它能够检测到主线程空闲状态的时候,以便在适当的时候运行你的测试代码或命令,这样你就没必要通过sleep去让主线程睡眠的方式去同步测试。说白了就是Espresso框架测试app不会通过阻塞主线程去同步UI测试。
Espresso有三种重要体系的类,分别是Matchers(匹配器),ViewAction(界面行为),ViewAssertions(界面判断),其中Matchers是常常是通过匹配条件来需找UI组件或过滤UI,而ViewAction是来模拟用户操作界面的行为,ViewAssertions对模拟行为操作的View进行变换和结果验证,其三者关系如图所示:

具体Espresso使用文档和Android 官方使用可以参考下面官方资料:
https://google.github.io/android-testing-support-library/docs/index.html
http://developer.android.com/training/testing/ui-testing/espresso-testing.html

Espresso实战

模拟用户行为测试:
代码:

package com.scau.beyondboy.idgoods;

import android.support.test.espresso.contrib.PickerActions;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import android.widget.DatePicker;

import org.hamcrest.Matchers;
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.clearText;
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.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;

/**
 * Author:beyondboy
 * Gmail:xuguoli.scau@gmail.com
 * Date: 2015-10-25
 * Time: 16:21
 */
//设置测试运行环境
@RunWith(AndroidJUnit4.class)
public class PersonInforTest

    private static final String TAG = PersonInforTest.class.getName();
    //设置启动的Activity
    @Rule
    public ActivityTestRule<MainActivity> mActivityTestRule=new ActivityTestRule<MainActivity>(MainActivity.class);
    //模拟用户的点击行为
    private void clicktest(final int id)
    
        onView(withId(id)).perform(click());
    
    //改变View的文本显示
    private String changetexttest(final int id,String text)
    
        onView(withId(id)).perform(clearText(),typeText(text),closeSoftKeyboard());
        return text;
    
    //检查View文本变化是否正确
    private void checktexttest(final int id,String text)
    
        onView(withId(id)).check(matches(withText(text)));
    

    //测试方法
   @Test
    public void NickNameTest()
   
       clicktest(R.id.menu_toggle);
       clicktest(R.id.header_image);
       clicktest(R.id.nickname_layout);
       final String text=changetexttest(R.id.nickname,"test");
       clicktest(R.id.save);
       checktexttest(R.id.nickname, text);
   


运行结果:

测试成功会显示绿条:

测试失败会显示红褐色条并报出对应的错误信息:

Espresso异步测试:
在开发中,我们经常会碰到异步请求,等异步请求完成后,再去更新UI,那么测试如何进行,估计很多测试新手可能会想到用Thread.sleep()方法让主线程睡眠等待,直到异步请求完成,刚开始我也是这么想的,但仔细想想这种方法是不对啊,我们知道主线程阻塞不能超过五秒,已超过五秒就会引起ANR异常,这种方式明显是不可行,那么有没有更好的方法呢?有,那就是通过Espresso的IdlingResource类来去完成,那是专门处理测试中的异步操作的发生,里面有两个相当重要的方法,其解释如下:
public void registerIdleTransitionCallback(ResourceCallback callback); 这个方法注册回调。
其回调接口:

 public interface ResourceCallback 
    /**
     * Called when the resource goes from busy to idle.
     */
    public void onTransitionToIdle();
  

public boolean isIdleNow(); 此方法通常用来通知主线程,其异步操作的完成,好让主线程更新UI,返回true便通知主线程去更新UI线程。
代码:

package com.scau.beyondboy.idgoods;

import android.support.test.espresso.IdlingResource;

import com.scau.beyondboy.idgoods.view.SlideListView;

/**
 * Author:beyondboy
 * Gmail:xuguoli.scau@gmail.com
 * Date: 2015-12-16
 * Time: 13:34
 * 实现ListView异步空闲处理类
 */
public class ListAdapterIdlingResource implements  IdlingResource

    private SlideListView mSlideListView;
    private IdlingResource.ResourceCallback mCallback;
    private final long startTime;
    private final long waitingTime;
    public  ListAdapterIdlingResource(long waitingTime,SlideListView slideListView)
    
        this.startTime = System.currentTimeMillis();
        this.waitingTime = waitingTime;
        this.mSlideListView=slideListView;
    

    @Override
    public String getName()
    
        return "listadapterIdlingResource";
    

    @Override
    public boolean isIdleNow()
    
        //当网络数据加载完,才设置适配器,故可以通过适配器是否为空值来判断其异步数据加载是否完成
        if(mSlideListView.getAdapter()!=null)
        
            mCallback.onTransitionToIdle();
            System.out.println("打印");
            return true;
        
        return false;
        //通过时间来限制其异步加载
        /*long elapsed = System.currentTimeMillis() - startTime;
        boolean idle = (elapsed >= waitingTime);
        if (idle) 
            System.out.println("打印");
            mCallback.onTransitionToIdle();
        
        return idle;*/
    

    @Override
    public void registerIdleTransitionCallback(ResourceCallback callback)
    
        this.mCallback=callback;
    



package com.scau.beyondboy.idgoods;

import android.support.test.espresso.Espresso;
import android.support.test.espresso.IdlingPolicies;
import android.support.test.espresso.IdlingResource;
import android.support.test.espresso.matcher.BoundedMatcher;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;

import com.scau.beyondboy.idgoods.view.SlideListView;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.Map;
import java.util.concurrent.TimeUnit;

import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.clearText;
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.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static org.hamcrest.Matchers.anything;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasEntry;

/**
 * Author:beyondboy
 * Gmail:xuguoli.scau@gmail.com
 * Date: 2015-12-14
 * Time: 20:16
 */
@RunWith(AndroidJUnit4.class)
public class ProductTest

    private static final String TAG = ProductTest.class.getName();
    @Rule
    public ActivityTestRule<MainActivity> mActivityTestRule=new ActivityTestRule<MainActivity>(MainActivity.class);
    private void clicktest(final int id)
    
        onView(withId(id)).perform(click());
    

    private String changetexttest(final int id, String text)
    
        onView(withId(id)).perform(clearText(),typeText(text),closeSoftKeyboard());
        return text;
    

    private void checktexttest(final int id,String text)
    
        onView(withId(id)).check(matches(withText(text)));
    
    @Test    
    public void testClickOnItem()
    
        clicktest(R.id.menu_toggle);
        clicktest(R.id.myproduct);
        int waitingTime=1000;
        //设置异步操作测试超时时间
        IdlingPolicies.setMasterPolicyTimeout(
                waitingTime * 10 TimeUnit.MILLISECONDS);
        IdlingPolicies.setIdlingResourceTimeout(
                waitingTime * 10, TimeUnit.MILLISECONDS);
        IdlingResource idlingResource=new ListAdapterIdlingResource(1000,(SlideListView)mActivityTestRule.getActivity().findViewById(R.id.product_slidelistview));
        //等待后台ListView加载完数据后执行后面的代码
        Espresso.registerIdlingResources(idlingResource);
        //选中一个listView的item选项           onData(anything()).inAdapterView(withId(R.id.product_slidelistview)).atPosition(10).perform(click());
        //释放对其异步空闲处理类
        Espresso.unregisterIdlingResources(idlingResource);
        clicktest(R.id.header_image);
        checktexttest(R.id.product_name, "其他的商品");
        pressBack();
        pressBack();        
        

运行结果如图:

Espresso Intent测试:
代码:

package com.scau.beyondboy.idgoods;

import android.support.test.espresso.intent.Intents;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;

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.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static android.support.test.espresso.matcher.ViewMatchers.withId;

/**
 * Author:beyondboy
 * Gmail:xuguoli.scau@gmail.com
 * Date: 2016-01-07
 * Time: 12:57
 * 第一种Intent测试
 */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class IntentTest

    @Rule
    public final ActivityTestRule<MainActivity> rule =
            new ActivityTestRule<>(MainActivity.class);   
    @Test
    public void intentTest()
    
        //这种必须调用Intetns.init()方法
        Intents.init();
        clicktest(R.id.menu_toggle);
        clicktest(R.id.header_image);
        intended(hasComponent(PersonInfoActivity.class.getName()));
        clicktest(R.id.email_layout);
        intended(hasComponent(ChangeEmailActivity.class.getName()));
        Intents.release();
    
    private void clicktest(final int id)
    
        onView(withId(id)).perform(click());
    





package com.scau.beyondboy.idgoods;

import android.support.test.espresso.intent.Intents;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.test.suitebuilder.annotation.LargeTest;

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.intent.Intents.intended;
import static android.support.test.espresso.intent.matcher.IntentMatchers.hasComponent;
import static android.support.test.espresso.matcher.ViewMatchers.withId;

/**
 * Author:beyondboy
 * Gmail:xuguoli.scau@gmail.com
 * Date: 2016-01-07
 * Time: 12:57
 * 第二种Intent测试
 */
@RunWith(AndroidJUnit4.class)
@LargeTest
public class IntentTest
    
    @Rule
   public IntentsTestRule<MainActivity> mRule = new IntentsTestRule<>(MainActivity.class);
    @Test
    public void intentTest()
           
        clicktest(R.id.menu_toggle);
        clicktest(R.id.header_image);
        intended(hasComponent(PersonInfoActivity.class.getName()));
        clicktest(R.id.email_layout);
        intended(hasComponent(ChangeEmailActivity.class.getName()));        
    
    private void clicktest(final int id)
    
        onView(withId(id)).perform(click());
    

运行结果:

参考资料:
http://michaelevans.org/blog/2015/09/15/testing-intents-with-espresso-intents/
https://github.com/JakeWharton/double-espresso/tree/gradle/espresso-sample/src/androidTest/java/com/google/android/apps/common/testing/ui/testapp

以上是关于Android Espresso单元测试的主要内容,如果未能解决你的问题,请参考以下文章

使用Espresso Test Recorder编写Android测试

在ANDROID STUDIO环境下使用JUNIT框架进行单元测试

Android测试:Espresso 自动化测试

Android - Espresso:为每次测试重新创建活动

Android 自动化测试 Espresso篇:异步代码测试

Android Studio 2.2 Espresso Test Recorder-----解放双手,通过录制测试过程实现测试