carter_dream的博客

专心做一个会技术的tester

【Android测试】【第十三节】Uiautomator——项目实战
前言

  前面我们已经了解Uiautomator的基本知识,并且学习了API的用法,因此对于我们来说完成一个UI自动化测试脚本并不难,但是如何将UI自动化应用在实际的项目中,帮我们提高测试的效率呢?本节我们就说说,UI自动化应该怎么去完成。

  我们以微信"小视屏"这个功能为例,来完成本次自动化测试的讲解。(鉴于隐私原因,默认在执行脚本前,微信已经是登录状态)


分析


  当我们要完成一个自动化时,需要考虑这个用例需要怎么设计,需要测试哪些项,怎么验证,出现错误时应该如何处理。

  首先需要明确一点,并不是所有需求文档上提到的功能,我们都必须用自动化方式去验证,由于UI自动化本身的局限性,UI自动化的可行度不是100%的准确,因此我们只对“小视屏”的卖点功能进行自动化验证,你也可以理解为对该功能做一个冒烟测试。

  小视屏功能的入口一共是三个,分别是下面这三个地方:

  

  我们除了要验证这地方的入口外,还需要在其中一处完成对小视屏的发送,并且验证小视屏发送成功。因此我们可以按照下面流程来进行测试脚本的编写,流程图如下所示:

 

 

 

编码前准备


  有了流程图之后,不要迫不及待的编码。编码之前也需要考虑考虑,是否有一些公共的方法可以提取出来做为一个单独的函数呢?

1、点击操作

  首先,点击的操作是Uiautomator中用的最多的,而根据控件id和text来做为索引则是更多的。因此我们封装如下的内容:

    /* 定义“通过哪种方式来获得uiselector”的int标识,
       如果以后想添加别的方法(例如 通过description 来获取),则可以参考此形式进行扩充 */

    final int CLICK_ID = 2000;
    final int CLICK_TEXT = 2001;

    /* 实现具体的外部可以调用的函数 */

    // 通过id来进行点击操作
    public boolean ClickById(String id)
    {
        return ClickByInfo(CLICK_ID, id);
    }
    // 通过text来进行点击操作
    public boolean ClickByText(String text)
    {
        return ClickByInfo(CLICK_TEXT, text);
    }

    /* 封装出通用的点击方法,供上面的public函数调用
       如果以后想添加别的方法(例如 通过description 来获取),则可以在switch中扩充 */
    private boolean ClickByInfo(int CLICK, String str)
    {
        UiSelector uiselector = null;
        // switch根据不同的CLICK标识,创建出UiSelector的对象
        switch(CLICK)
        {
        case CLICK_ID:   uiselector = new UiSelector().resourceId(str); break;
        case CLICK_TEXT: uiselector = new UiSelector().text(str);       break;
        default: return false;
        }
        // 根据UiSelector对象构造出UiObject的对象
        UiObject uiobject = new UiObject(uiselector);
        // 判断该控件是否存在
        if(!uiobject.exists())
        {
            return false;
        }
        // 点击
        try
        {
            uiobject.click();
        } catch (UiObjectNotFoundException e)
        {
            e.printStackTrace();
        }
        return true;
    }

  使用上面我的方法封装之后,你只需要调用  ClickByText("通讯录");  即可完成对"通信录" 这个控件的点击,并且在因为异常情况获取不到该控件的时候,也不会报出异常。

  然而,我们去点击一个控件的时候,当它出现找不到的情况的时候,这有可能就是bug了,我们需要将其记录下来,并且记录下当时的现场,一般采用截图的方法,以便我们查问题时候能更直观的了解到当时机器一个运行情况。因此接下来,我要说说截图和异常处理。

2、截屏和异常处理

  上面的代码中,当UiObject对象找不到的时候,我们只是返回了一个false,告诉调用者这次调用失败了,但是为什么失败,怎么避免这样的失败,并没有记录下来。因此在这段代码中,我们需要加以下的内容:

    private boolean ClickByInfo(int CLICK, String str)
    {
        ....
        // 判断该控件是否存在
        if(!uiobject.exists())
        {
            TakeScreen(getUiDevice(), str+"-not-find");
            return false;
        }
       ....
    }
    
    /* 保存屏幕截图
       参数descrip 为 描述该截图的内容 */
    public void TakeScreen(UiDevice device, String descrip) 
    {
        // 取得当前时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        String datestr = calendar.get(Calendar.HOUR_OF_DAY) + "_" + calendar.get(Calendar.MINUTE) + "_" + calendar.get(Calendar.SECOND);
        
        // 保存文件
        File files = new File("/mnt/sdcard/"+datestr+"_"+descrip+".jpg");    
        device.takeScreenshot(files);        
    }

  这样当我们在调用 ClickByText("通讯录"); 找不到控件的时候,我们的脚本就会自动截取当时屏幕的图像保存在我们的手机中(如下图),这样我们只需打开图片,就知道当时发生了什么,为什么没有找到该控件:

   

   看似完美的方案,其实在实际运行中只是帮我们记录了这个控件这一时刻点击失败的原因,而我们想要的是,脚本在调用了这个方法后,尽最大的可能帮我们点击成功。举一个简单的例子:

  这是我们写脚本中经常遇到的一个问题,我们需要 ‘在A页面上点击“进入”按钮,跳转到B页面,然后点击B页面上的“保存”按钮’ 完成我们的操作。

  一般我们的写法是:

ClickByText("进入");
ClickByText("保存");

  然而当我们的手机特别卡,或者是页面承载太多东西的时候,当你调用了点击“进入”按钮后,B页面没有及时的跳转出来,这个时候调用B页面上的“保存”按钮,就会出现异常,而如果你没有按照我上面的方案去实现的话,系统就会抛出异常,而使用了我上面的方案之后,系统虽然不会抛出异常,而且会在你找不到B页面的“保存”按钮时截取当前的屏幕,你完全可以根据截图来判断出来:当是没有找到“保存”按钮的原因是,当时的B页面还没有跳转出来。然而在这个时候,我最希望的并不是看到日志告诉我说哪里哪里失败了,而是想让这次的点击效果生效。

  那么怎么解决这个问题呢?相信很多亲手写过Uiautomator脚本的朋友都知道,在两个操作直接加如sleep,没错,这是解决方案,那么究竟应该slepp多久呢?因为不同的手机响应时间是不一样的,如果sleep太短就依然存在上述问题;如果sleep太长的话,无疑使得脚本的运行变的缓慢,多出写无用的sleep。因此我们需要下面的方案解决:

    private boolean ClickByInfo(int CLICK, String str)
    {
        ....
        // 判断该控件是否存在
        if(!uiobject.exists())
        {
            TakeScreen(getUiDevice(), str+"-not-find");
            return false;
        }
        int i = 0;
        while (!uiobject.exists() && i<5)
        {                    
            sleep(500);
            if (i== 4)
            {
                TakeScreen(getUiDevice(), str+"-not-find");
        return false;  } i
++; } .... }

  我们去掉了if判断的代码,改为在while循环中等待这个控件的出现,一共等待5次,如果到了第五次,它还没有出现的话,那么我们就认为它真的不会出现了,这个时候去截屏比第一次就没有找到更加的有意义。当然如果你还想提高你的UI自动化的健壮性,那么这里还可以加一个类似这样的函数:

    /* 封装出通用的点击方法,供上面的public函数调用
       如果以后想添加别的方法(例如 通过description 来获取),则可以在switch中扩充 */
    private boolean ClickByInfo(int CLICK, String str)
    {
        ....
        // 判断该控件是否存在      
        int i = 0;
        while (!uiobject.exists() && i<5)
        {
            SolveProblems();
            sleep(500);
            if (i== 4)
            {
                TakeScreen(getUiDevice(), str+"-not-find");
                return false;
            }
            i++;        
        }
        ....
    }
    
    /**
     * 当进不下去的时候,使用该方法,例如可能是出现了一些对话框遮挡,该方法会把对话框干掉*/
    private void SolveProblems()
    {        
        ....
    }

  这个 SolveProblems() 函数主要是用来解决一些“麻烦”的,例如我们在操作地图的时候,当gps信号不好的时候,就会弹出下面的对话框: 

  由于出现的对话框,遮挡住了我们的Activity,影响我们对界面上ui元素的获取,这个时候,我们就可以在SolveProblems() 加入这样一断逻辑:当出现“开启gps”对话框的时候,就点击“残忍的拒绝”,将此对话框给关掉,这样while的判断条件再次执行的时候,就可以成功获取到你想要的元素。下面这段对话主要为了加深你对SolveProblems() 这个函数的理解:

  所以说这个SolveProblems()才是提高UI自动化成功率的关键,因为每个App都有自己的特征,因此这部分的内容,需要你们在平时的日积月累中才能总结出来,当你有了一个足够多的经验库之后,你的App几乎不会再因为外界因素而导致失败了。经过我自己在我项目上的尝试,效果非常的显著。

3、日志

  日志的重要性不言而喻,当我们在自动化执行的过程中,肯定不会一直盯着屏幕观察,因此日志使我们最依靠的东西。关于日志的记录方法多种多样,我这里提供下我是怎么在Uiautomator中打印日志的:

  public String m_logpathString = "/mnt/sdcard/PerformanceLog.txt";

  public
void UiAutomationLog(String str) { // 取得当前时间 Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); String datestr = calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND) + calendar.get(Calendar.MILLISECOND) + ":"; FileWriter fwlog = null; try { fwlog = new FileWriter(m_logpathString, true); fwlog.write(datestr + str + "\r\n"); System.out.println(datestr + str); fwlog.flush(); } catch (IOException e) { e.printStackTrace(); } finally { try { fwlog.close(); } catch (IOException e) { e.printStackTrace(); } } }

  接下来就是把这个函数加在一些关键的地方,当出错的时候,方便我们排查问题即可。下面是我日常跑脚本打出来的日志:

 

  总结上面的所有代码,我们把这些放到一个公共的方法中,这样我们的脚本就可以直接引入这个类,然后直接的进行调用这些函数了。

  附上源代码:

package QQ;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;

import com.android.uiautomator.core.UiDevice;
import com.android.uiautomator.core.UiObject;
import com.android.uiautomator.core.UiObjectNotFoundException;
import com.android.uiautomator.core.UiSelector;
import com.android.uiautomator.testrunner.UiAutomatorTestCase;

public class UiautomatorAssistant extends UiAutomatorTestCase
{
    /* log地址 */
    public String m_logpathString = "/mnt/sdcard/PerformanceLog.txt";
    
    /* 定义“通过哪种方式来获得uiselector”的int标识,
       如果以后想添加别的方法(例如 通过description 来获取),则可以参考此形式进行扩充 */

    final int CLICK_ID = 2000;
    final int CLICK_TEXT = 2001;

    /* 实现具体的外部可以调用的函数 */

    // 通过id来进行点击操作
    public boolean ClickById(String id)
    {
        return ClickByInfo(CLICK_ID, id);
    }
    // 通过text来进行点击操作
    public boolean ClickByText(String text)
    {
        return ClickByInfo(CLICK_TEXT, text);
    }

    /* 封装出通用的点击方法,供上面的public函数调用
       如果以后想添加别的方法(例如 通过description 来获取),则可以在switch中扩充 */
    private boolean ClickByInfo(int CLICK, String str)
    {
        UiSelector uiselector = null;
        // switch根据不同的CLICK标识,创建出UiSelector的对象
        switch(CLICK)
        {
        case CLICK_ID:      uiselector = new UiSelector().resourceId(str); break;
        case CLICK_TEXT: uiselector = new UiSelector().text(str); break;
        default: return false;
        }
        // 根据UiSelector对象构造出UiObject的对象
        UiObject uiobject = new UiObject(uiselector);
        // 判断该控件是否存在
        int i = 0;
        while (!uiobject.exists() && i<5)
        {
            SolveProblems();
            sleep(500);
            if (i== 4)
            {
                TakeScreen(getUiDevice(), str+"-not-find");
                return false;
            }
            i++;        
        }
        // 点击
        try
        {
            UiAutomationLog("click type:"+CLICK+" content:"+str );
            uiobject.click();
        } catch (UiObjectNotFoundException e)
        {
            e.printStackTrace();
        }
        return true;
    }
    
    /* 当进不下去的时候,使用该方法,例如可能是出现了一些对话框遮挡,该方法会把对话框干掉 */
    private void SolveProblems()
    {        
        
    }
    
    /* 保存屏幕截图
         参数descrip 为 描述该截图的内容 */
    public void TakeScreen(UiDevice device, String descrip) 
    {
        // 取得当前时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        String datestr = calendar.get(Calendar.HOUR_OF_DAY) + "_" + calendar.get(Calendar.MINUTE) + "_" + calendar.get(Calendar.SECOND);
        
        // 保存文件
        File files = new File("/mnt/sdcard/"+datestr+"_"+descrip+".jpg");    
        UiAutomationLog("TakeScreen: " + datestr+"_"+descrip+".jpg");
        device.takeScreenshot(files);        
    }
    
    /* 打log记录在手机中 */
    public void UiAutomationLog(String str) 
    {
        // 取得当前时间
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        String datestr = calendar.get(Calendar.HOUR_OF_DAY) + ":" + calendar.get(Calendar.MINUTE) + ":" + calendar.get(Calendar.SECOND) + calendar.get(Calendar.MILLISECOND) + ":";

        FileWriter fwlog = null;
        try
        {
            fwlog = new FileWriter(m_logpathString, true);
            fwlog.write(datestr + str + "\r\n");
            System.out.println(datestr + str);
            fwlog.flush();

        } catch (IOException e)
        {
            e.printStackTrace();
        } finally
        {
            try
            {
                fwlog.close();
            } catch (IOException e)
            {
                e.printStackTrace();
            }
        }
    }
}
UiautomatorAssistant.java

 

  未完,待续....

 

阅读更多
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/carter_dream/article/details/50295981
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

不良信息举报

【Android测试】【第十三节】Uiautomator——项目实战

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭