51Testing软件测试论坛

标题: 好用的安卓自动化测试框架Espresso [打印本页]

作者: lsekfe    时间: 2022-4-2 10:56
标题: 好用的安卓自动化测试框架Espresso
Android测试概述
  Android自动化测试是应用开发中提升开发效率的一种方式,它可以在短时间内跑完所有的测试方案以及实现各种交互,从而让开发者更加方便和直观的知道代码的实现效果。
  Android自动化测试可以分为单元测试、UI测试、Monkey测试三种,同时也分小中大三种测试。
[attach]137183[/attach]
单元测试
  单元测试又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作,程序单元是应用的最小可测试部件。
  Android中的单元测试是基于JUnit,可分为本地测试和Instrumented 测试。
  本地测试对应的项目目录是module-name/src/test/java/,本地测试是运行在本地JVM上的,不需要模拟器或者设备的支持Instrumented 测试在项目中对应的目录是module-name/src/androidTest/java/,这个测试包含了Android系统的api,所以要在模拟器或者设备上运行,速度比本地测试慢。
[attach]137184[/attach]
单元测试最常用的框架就是JUnit跟Mockito组合的测试框架,其中JUnit用来跑主要的测试代码,Mockito可以创建特定测试对象(非对象本身)并设置一些特定的属性供测试使用。

  UI测试
  UI测试是测试应用中的各种交互是否达到了实现的效果。常用的UI测试框架有Espresso和UIAutomator。
  UIAutomator是一个界面测试框架,适用于整个系统上以及多个已安装应用间的跨应用功能界面测试,它提供了一组 API,用于构建在用户应用和系统应用上执行交互的界面测试。
  Espresso是Google的开源自动化测试框架。相对于UIAutomator,它的特点是规模更小、更简洁,API更加精确,编写测试代码简单,容易快速上手。因为他是基于Instrumentation的,所以不能跨App。

  Espresso的使用
  Espresso有三个重要的类:分别是Matchers(匹配器)、ViewAction(界面行为)、ViewAssertions(界面判断)。
  其中Matchers是常常是通过匹配条件来找UI组件或过滤UI,而ViewAction是来模拟用户操作界面的行为,ViewAssertions对模拟行为操作的View进行变换和结果验证。
  执行顺序如下:
[attach]137185[/attach]
[attach]137186[/attach]
依赖和配置
[attach]137187[/attach]
在导入好包之后就可以module的AndroidTest包下开始编写自己的UI自动化测试代码了。

  测试界面跳转
  测试界面跳转时需要用的Espresso-Intents库,它是Espresso的扩展程序,支持对被测应用发出的intent进行验证和打桩。
  它与Mockit 类似,但适用于Android Intent,Espresso-Intents有两个比较重要的方法itending()和intended()。

  intending() 该方法可以插桩(设置跳到其他Activity后返回的参数),原来的Activity调用startActivityForResult()触发桩测试。
  intended() 方法是对发送出去的intent进行确认,如果不是给出的intent则会报错。
  1.     [url=home.php?mod=space&uid=724]@test[/url] fun validateIntentSentToPackage() {
  2.         user.clickOnView(system.getView(R.id.callButton))
  3.         intended(toPackage("com.android.phone"))
  4.     }
复制代码
  异步测试
  Espresso测试有个很强大之处就是它在多个测试操作中是线程安全的,它会等待当前进程的消息队列中的UI事件,并且在任何一个测试操作中会等待其中的异步任务结束才会执行下一个测试。
  Espresso异步测试是要导入以下两个包:
[attach]137188[/attach]
异步测试的流程
  新建一个类继承IdlingResource:
  1. public class SimpleIdlingResource implements IdlingResource {
  2.     private final String mResourceName;

  3.     //这个counter值就像一个标记,默认为0
  4.     private final AtomicInteger counter = new AtomicInteger(0);

  5.     private volatile ResourceCallback resourceCallback;

  6.     public SimpleIdlingResource(String resourceName) {
  7.         mResourceName = resourceName;
  8.     }

  9.     @Override
  10.     public String getName() {
  11.         return mResourceName;
  12.     }

  13.     @Override
  14.     public boolean isIdleNow() {
  15.         return counter.get() == 0;
  16.     }

  17.     @Override
  18.     public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
  19.         this.resourceCallback = resourceCallback;
  20.     }

  21.     //每当我们开始异步请求,把counter值+1
  22.     public void increment() {
  23.         counter.getAndIncrement();
  24.     }

  25.     //当我们获取到网络数据后,counter值-1;
  26.     public void decrement() {
  27.         int counterVal = counter.decrementAndGet();
  28.         //如果这时counter == 0,说明异步结束,执行回调。
  29.         if (counterVal == 0) {
  30.             //
  31.             if (null != resourceCallback) {
  32.                 resourceCallback.onTransitionToIdle();
  33.             }
  34.         }

  35.         if (counterVal < 0) {
  36.             //如果小于0,抛出异常
  37.             throw new IllegalArgumentException("Counter has been corrupted!");
  38.         }
  39.     }
  40. }
复制代码
 新建一个管理类去管理上述类:

  1. public class EspressoIdlingResource {
  2.     private static final String RESOURCE = "GLOBAL";

  3.     private static SimpleIdlingResource mCountingIdlingResource =
  4.             new SimpleIdlingResource(RESOURCE);

  5.     public static void increment() {
  6.         mCountingIdlingResource.increment();
  7.     }

  8.     public static void decrement() {
  9.         mCountingIdlingResource.decrement();
  10.     }

  11.     public static IdlingResource getIdlingResource() {
  12.         return mCountingIdlingResource;
  13.     }
  14. }
复制代码
 将管理类插入到异步代码中,在异步任务开始前插入代码:

  1. protected void onReqStart(int where) {
  2.    //异步开始前
  3.        EspressoIdlingResource.increment();
  4.    }
复制代码
异步任务结束后插入代码:

  1.   private void onFinish() {
  2.        if(!EspressoIdlingResource.getIdlingResource().isIdleNow()){
  3.            EspressoIdlingResource.decrement();
  4.        }
  5.    }
复制代码
在测试代码中注册IdlingRecource:

  1. @Before
  2.    public void setUp() throws Exception {
  3.        //调用Activity中我们已经设置好的getIdlingresource()方法,获取Idlingresource对象
  4.        idlingresource = activityRule.getActivity().getIdlingresource();

  5.        //去掉下行注释,只有异步结束后,才进行接下来的测试代码(tests passed)
  6.        //注册异步监听,当该idlingresource中的counter标记值为0时才进行接下来的测试代码
  7.        Espresso.registerIdlingResources(idlingresource);
  8.    }

  9.    @Test
  10.    public void onLoadingFinished() throws Exception {
  11.        //  不再需要这样的代码
  12.        //  Thread.sleep(5000);

  13.        // 未注册idlingResource时,立即进行test,此时异步并未结束,报错(tests failed)
  14.        onView(withId(R.id.text))
  15.                .check(matches(withText("success!")));
  16.    }

  17.    @After
  18.    public void release() throws Exception {
  19.        //我们在测试结束后取消注册,释放资源
  20.        Espresso.unregisterIdlingResources(idlingresource);
  21.    }
复制代码
 测试过程中遇到的问题
  ViewAction()中的typeText()使用。
  如果直接调用typeText(),那么它的使用对象就一定是editText,如果是自定义的未继承editText的view使用typeText()时,要在使用前supportsInputMethods(),否则会报错误:
  1. onView(allOf(supportsInputMethods(), isDescendantOfA(withId(R.id.test)))).perform(typeText("111111"))
复制代码
在调用完typeText()后最好调用一下 closeSoftKeyboard(),否则会出现某些view在点击时被阻挡而报错。


  总结
  自动化测试的目的不仅仅是解放双手,它也可以让开发者提前知道某些小Bug,不用到了测试阶段才被提出来,这样就提升了开发的效率。









作者: bsackk    时间: 2022-4-8 11:50
谢谢楼主分享




欢迎光临 51Testing软件测试论坛 (http://bbs.51testing.com/) Powered by Discuz! X3.2