The fifth personal blog Android studio test software --Ui Automator test for learning and use

Week 15 in the personal blog requires us to introduce software testing software based on the final team with a big job (Android studio), I chose Ui Automator Android software test automation testing based on Android, which will be following me a brief introduction.

1, UiAutomator Profile

Uiautomator is Google launched a tool for automated testing UI, which is the common manual testing, click on each control element look at the output of the results are in line with expectations. For example, the login screen and enter the correct respectively incorrect user name and password and click the login button can log in and see if any error messages and so on.

It has the following advantages:

1. The clear API, provides precise control and flexible;

2. No source code;

3 may be scheduled at different among the App;

4. UIAutomator support scripting language is Java, and Java is an object-oriented, very broad universal language;

UIAutomator functional testing test process:

2, UiAutomator ready

Integrated UI Automator, you first need to ensure that the project has been dependent on the App Gradle Testing. Then add the following to rely on the gradle.

3, the UI elements look

In Android development, you can kind of findViewById to find the appropriate controls, but UI Automator is used to test the interface, if also used the same way, then it will depend a lot. UI Automator uses a similar approach to find elements. UI Automator is looking for based on text elements, hint, contentDescription and other property, in order to make UI Automator can obtain more property value, test the UI interface is accessible, while UI Automator can also access these control elements.

You can use uiautomatorviewer, to get the hierarchy of interface elements and attributes of each element, then UI Automator you can find the corresponding control element according to the attribute information.

Acquisition step of: 
* on the App boot device 
* connecting the device to a development machine 
* Open Android sdk directory, <android-sdk> / tools  /.
* Start uiautomatorviewer 
can then operate uiautomatorviewer 
* Click Device ScreenShot, wait a minute, to get a snapshot of the screen, right of the interface is the current layout of the interface structure and attribute information.

Ensure that the UI can be accessed 
introduced above, UI Automator is based on text elements hint, contentDescription and other property were looking for, we need to add to the above attributes as layout elements. Sometimes programmers need to customize some controls, then please realize AccessibilityNodeProvider, in order to ensure normal use.

4, using the example of a test UiAutomator

The project needs to be at least 100 mobile phones for application compatibility testing, the first thought is to automate the operation, do not want the same operation a station repeat the operation.

The basic requirement is that, install the application being tested, up and quit, and then install a test sample, to detect whether there is a corresponding pop interception.

Here is the basic testing process

# Adb need to configure the environment variables 
# 1 to determine several cell phone 
# 2 and then determine how many application 
# 3 to install mkiller, start mkiller 
# 4. re-install the test sample 
# 5. Check if there is to cancel the installation the button appears, it appears that testing by, did not appear that testing fails

 

As to the use of automation, we can not test the phone as a running one, and should be able to simultaneously run multiple cell phone, my idea is to enable multiple threads to run each thread to run a mobile phone

OK there are a few mobile phones.


def finddevices():

    rst = util.exccmd('adb devices')

    devices = re.findall(r'(.*?)\s+device',rst)

    if len(devices) > 1:

        deviceIds = devices[1:]

        logger.info ( 'Total% s mobile phone'% str (len (devices) -1))

        for i in deviceIds:           

            logger.info ( 'ID was% s'% i)

        return deviceIds

    else:

        logger.error ( 'did not find the phone, check')

        return

 

Here says uiautomator use in python, in fact github of readme.md written in very clear, but in practice it still has some problems

uiautomator when in use must initialize a d objects, through a single phone

Multiple phones can

 Then d = d initialize objects Device (Serial), the subsequent operations are basically the operation the object is d, d corresponding to each conceivable that a mobile phone

Basic clicks

 

# press home key

d.press.home()

# press back key

d.press.back()

# the normal way to press back key

d.press("back")

# press keycode 0x07('0') with META ALT(0x02) on

d.press(0x07, 0x02)

First, start the application installation, install using adb install command to start using adb shell am start command

Phone first aid kit launchable-activity is 'com.qihoo.mkiller.ui.index.AppEnterActivity', the first time you start using the protocol will pop up to the user to click "Agree and use"

I used here a watcher to monitor and click method is basic watcher

d.watcher ( 'agree'). when (text = u 'and agree to use') .click (text = u 'and agree to use')

Watcher give a name, just starting, I called here agree, when conditions inside write, I have here is that when text 'consent and use' is, when write back operation performed when these conditions are met, I am here is to click (text = u 'consent and use'), there is a pit, before I write watcher of time, write directly click () do not write the contents of which I think will click on the default element found earlier, but later found that this is not acceptable, you must write to click on which object

In fact, for this only appears once in the view can not write watcher where you can write directly d (text = u 'consent and use') .click (), but considering there will be some delay before the screen appears, all kinds of mobile phones different performance, not good plus time.sleep () time, so I recommend all like this watcher written in, and when it appears when clicking.

Since this application will request root privileges, so sometimes third-party root tools will play the corresponding authorization prompt box, I think most of the root tools should all have the "Allow" button, so I added a watcher

d.watcher ( 'allowroot'). when (text = u 'allow') .click (text = u 'allowed')

After clicking the consent will open again playing a super model playing box, click here I want to cancel

d.watcher ( 'cancel'). when (text = u 'cancel') .click (text = u 'Cancel')

之后要点击一下back键,这时又会弹一个是否退出的框,这次我要点击“确认”

这个确认我是后面单独处理的,其实也可以放在watcher里,只是我的考虑是有时点击back键的时候不一定会弹出来这个框,所以我会尝试多点击几次,直到这个框出来

但现在就有一个问题了,刚才写了一个d.watcher('cancel').when(text=u'取消').click(text=u'取消'),这时当弹出这个框的时候,watcher就要起作用了,就会先去点击取消,这不是我想要的,所以我将之前点击取消的加了一个限制条件

d.watcher('cancel').when(text=u'取消').when(textContains=u'超强防护能够极大提高').click(text=u'取消')

textContains的意思就是和包含里面的文字,上面的意思就是当界面中text是“取消”的同时还要有一个view的text中要包含u'超强防护能够极大提高',这样的话就限制的点击“取消”的条件,再遇到退出时的提示框就不会再会点击”取消”了

尽可能的想到可能出现的弹框,比较在小米手机中安装应用会弹一个小米的安装确认界面,使用下面的watcher来进行监测点击

d.watcher('install').when(text=u'安装').when(textContains=u'是否要安装该应用程序').click(text=u'安装',className='android.widget.Button')

总的watcher就是下面的样子

 

        d.watcher('allowroot').when(text=u'允许').click(text=u'允许')

        d.watcher('install').when(text=u'安装').when(textContains=u'是否要安装该应用程序').click(text=u'安装',className='android.widget.Button') #专门为小米弹出的安装拦截

        d.watcher('cancel').when(text=u'取消').when(textContains=u'超强防护能够极大提高').click(text=u'取消')

        d.watcher('confirm').when(text=u'确认').when(textContains=u'应用程序许可').click(text=u'确认')

        d.watcher('agree').when(text=u'同意并使用').click(text=u'同意并使用')

        d.watcher('weishiuninstall').when(textContains=u'暂不处理').click(textContains=u'暂不处理')

 

然后使用d.watchers.run()来启动watcher

但是在实际的watcher中,我发现这个watcher并没有想象的那样好用,有时经常是明明有相应的view但是就是点击不上,经过多次尝试,我发现,当界面已经出现的时候,这时我再强行的使用run()方法来启动watchers,这时它就能很好的点击了,所以基于此,我写了一个循环来来无限的调用run方法,times限制了次数,根据项目的实际进行调整吧,sleep时间也可以相应的调整

def runwatch(d,data):

    times = 120

    while True:

        if data == 1:               

            return True

        # d.watchers.reset()

        d.watchers.run()       

        times -= 1

        if times == 0:

            break

        else:

            time.sleep(0.5)

 

监视的时候又不能只跑监视程序,还要跑相应的测试步骤,所以这里我把这个runwatch方法放到一个线程中去跑,起一个线程用作监视,脚本的测试方法放在另外的线程上跑

#线程函数

class FuncThread(threading.Thread):

    def __init__(self, func, *params, **paramMap):

        threading.Thread.__init__(self)

        self.func = func

        self.params = params

        self.paramMap = paramMap

        self.rst = None

        self.finished = False

 

    def run(self):

        self.rst = self.func(*self.params, **self.paramMap)

        self.finished = True

 

    def getResult(self):

        return self.rst

 

    def isFinished(self):

        return self.finished

 

def doInThread(func, *params, **paramMap):

    t_setDaemon = None

    if 't_setDaemon' in paramMap:

        t_setDaemon = paramMap['t_setDaemon']

        del paramMap['t_setDaemon']

    ft = FuncThread(func, *params, **paramMap)

    if t_setDaemon != None:

        ft.setDaemon(t_setDaemon)

    ft.start()

    return ft

所以这里启动线程来跑runwatcher的调用就是

data = 0

doInThread(runwatch,d,data,t_setDaemon=True)

 

基本的思路就是这样,这样当脚本都写完了以后在单个手机上运行很好,但是一旦插入多个手机就会出现一个问题,所有watcher只在一台手机上有效,另外的手机就只能傻傻的不知道点击,这个问题困扰了很久,我在github上也给作者发issue,但是后来我自已找到了解决的办法,就是在d=Device(Serial)的时候加上local_port端口号,让每台手机使用不同的local_port端口号,这样各自运行各自的,都很完好

以下了测试脚本的代码

mkiller.py,主测试脚本文件

#coding:gbk

 

import os,sys,time,re,csv

import log

import util

from uiautomator import Device

import traceback

import log,logging

import multiprocessing

 

optpath = os.getcwd()                      #获取当前操作目录

imgpath = os.path.join(optpath,'img')      #截图目录

 

def cleanEnv():

    os.system('adb kill-server')

    needClean = ['log.log','img','rst']

    pwd = os.getcwd()

    for i in needClean:

        delpath = os.path.join(pwd,i)

        if os.path.isfile(delpath):

            cmd = 'del /f/s/q "%s"'% delpath

            os.system(cmd)

        elif os.path.isdir(delpath):

            cmd = 'rd /s/q "%s"' %delpath

            os.system(cmd)

    if not os.path.isdir('rst'):

        os.mkdir('rst')

       

def runwatch(d,data):

    times = 120

    while True:

        if data == 1:               

            return True

        # d.watchers.reset()

        d.watchers.run()       

        times -= 1

        if times == 0:

            break

        else:

            time.sleep(0.5)

           

def installapk(apklist,d,device):

    sucapp = []

    errapp = []

    # d = Device(device)

    #初始化一个结果文件

    d.screen.on()

    rstlogger = log.Logger('rst/%s.log'%device,clevel = logging.DEBUG,Flevel = logging.INFO)

    #先安装mkiller

    mkillerpath = os.path.join(os.getcwd(),'MKiller_1001.apk')

    cmd = 'adb -s %s install -r %s'% (device,mkillerpath)

    util.exccmd(cmd)

    def checkcancel(d,sucapp,errapp):

        times = 10

        while(times):

            if d(textContains = u'取消安装').count:

                print d(textContains = u'取消安装',className='android.widget.Button').info['text']

                d(textContains = u'取消安装',className='android.widget.Button').click()

                rstlogger.info(device+'测试成功,有弹出取消安装对话框')

                break

            else:

                time.sleep(1)

                times -= 1

                if times == 0:

                    rstlogger.error(device+'测试失败,没有弹出取消安装对话框')

    try:       

        d.watcher('allowroot').when(text=u'允许').click(text=u'允许')

        d.watcher('install').when(text=u'安装').when(textContains=u'是否要安装该应用程序').click(text=u'安装',className='android.widget.Button') #专门为小米弹出的安装拦截

        d.watcher('cancel').when(text=u'取消').when(textContains=u'超强防护能够极大提高').click(text=u'取消')

        d.watcher('confirm').when(text=u'确认').when(textContains=u'应用程序许可').click(text=u'确认')

        d.watcher('agree').when(text=u'同意并使用').click(text=u'同意并使用')

        d.watcher('weishiuninstall').when(textContains=u'暂不处理').click(textContains=u'暂不处理')

        # d.watchers.run()

        data = 0

        util.doInThread(runwatch,d,data,t_setDaemon=True)

        #启动急救箱并退出急救箱

        cmd = 'adb -s %s shell am start com.qihoo.mkiller/com.qihoo.mkiller.ui.index.AppEnterActivity'% device

        util.exccmd(cmd)

        time.sleep(5)

        times = 3

        while(times):

            d.press.back()

            if d(text=u'确认').count:

                d(text=u'确认').click()

                break

            else:

                time.sleep(1)

                times -=1

                

        for item in apklist:

            apkpath = item

            if not os.path.exists(apkpath):

                logger.error('%s的应用不存在,请检查'%apkpath)

                continue

            if not device:

                cmd = 'adb install -r "%s"' % apkpath

            else:

                cmd = 'adb -s %s install -r "%s"'%(device,apkpath)

            util.doInThread(checkcancel,d,sucapp,errapp)

            rst = util.exccmd(cmd)

    except Exception, e:

        logger.error(traceback.format_exc())

        data = 1

    data = 1

    return sucapp

           

def finddevices():

    rst = util.exccmd('adb devices')

    devices = re.findall(r'(.*?)\s+device',rst)

    if len(devices) > 1:

        deviceIds = devices[1:]

        logger.info('共找到%s个手机'%str(len(devices)-1))

        for i in deviceIds:           

            logger.info('ID为%s'%i)

        return deviceIds

    else:

        logger.error('没有找到手机,请检查')

        return

       

#needcount:需要安装的apk数量,默认为0,既安所有

#deviceids:手机的列表

#apklist:apk应用程序的列表

def doInstall(deviceids,apklist):

    count = len(deviceids)

    port_list = range(5555,5555+count)

    for i in range(len(deviceids)):

        d = Device(deviceids[i],port_list[i])

        util.doInThread(installapk,apklist,d,deviceids[i])

      

#结束应用

def uninstall(deviceid,packname,timeout=20):

    cmd = 'adb -s %s uninstall %s' %(deviceid,packname)

    ft = util.doInThread(os.system,cmd,t_setDaemon=True)

    while True:

        if ft.isFinished():

            return True

        else:

            time.sleep(1)

            timeout -= 1

            if timeout == 0:

                return False

 

if __name__ == "__main__":

    cleanEnv()

    logger = util.logger

    devicelist = finddevices()

    if devicelist:      

        apkpath = os.path.join(os.getcwd(),'apk')

        apklist = util.listFile(apkpath)

        doInstall(devicelist,apklist) #每个手机都要安装apklist里的apk

 

util.py 线程与执行cmd脚本函数

#coding:gbk

import os,sys

import log

import logging

import threading

import multiprocessing

import time

 

logger = log.Logger('log.log',clevel = logging.DEBUG,Flevel = logging.INFO)

 

def exccmd(cmd):

    try:

        return os.popen(cmd).read()

    except Exception:

        return None

   

#遍历目录内的文件列表

def listFile(path, isDeep=True):

    _list = []

    if isDeep:

        try:

            for root, dirs, files in os.walk(path):

                for fl in files:

                    _list.append('%s\%s' % (root, fl))

        except:

            pass

    else:

        for fn in glob.glob( path + os.sep + '*' ):

            if not os.path.isdir(fn):

                _list.append('%s' % path + os.sep + fn[fn.rfind('\\')+1:])

    return _list

   

#线程函数

class FuncThread(threading.Thread):

    def __init__(self, func, *params, **paramMap):

        threading.Thread.__init__(self)

        self.func = func

        self.params = params

        self.paramMap = paramMap

        self.rst = None

        self.finished = False

 

    def run(self):

        self.rst = self.func(*self.params, **self.paramMap)

        self.finished = True

 

    def getResult(self):

        return self.rst

 

    def isFinished(self):

        return self.finished

 

def doInThread(func, *params, **paramMap):

    t_setDaemon = None

    if 't_setDaemon' in paramMap:

        t_setDaemon = paramMap['t_setDaemon']

        del paramMap['t_setDaemon']

    ft = FuncThread(func, *params, **paramMap)

    if t_setDaemon != None:

        ft.setDaemon(t_setDaemon)

    ft.start()

    return ft

这个小测试应用虽然比较简单,但是由于刚刚接触uiautomator的python封装,所以还是遇到了一些麻烦,不过还好,最终的结果是很好的解决了相应的问题,这里也算是抛砖引玉吧,这个uiautomator还有很多好玩的值得探索的地方,待以后慢慢发现~

5、心得体会

Ui Automator的优点: 
* 可以对所有操作进行自动化,操作简单 
* 不需要对被测程序进行重签名,且,可以测试所有设备上的程序,比如~某APP,比如~拨号,比如~发信息等等 
* 对于控件定位,要比robotium简单一点点 

Ui Automator的缺点: 
* Ui Automator需要android level 16以上才可以使用,因为在level 16及以上的API里面才带有uiautomator工具 
* 如果想要使用resource-id定位控件,则需要level 18及以上才可以 
* 对中文支持不好(不代表不支持,第三方jar可以实现)

这次的个人博客,我认为是有一定的难度的,之前的软件质量与保障课程上我们学习并完成的是网页的测试软件介绍,例如Jmeter,连接数据库的JDBC等等,但是这次要测试的是安卓,虽然这学期有安卓课程的安排,但做测试我还觉得有一些困难的,通过问问同学以及查阅资料才最终解决了我的一些问题。

通过这次的作业,加上前几次的作业,从原型设计工具、源代码管理工具的学习到这次的软件测试环节,我体会到了这门课程的重要性,明白了真正成功的一款软件在真正发布之前做测试所耗费的经历不亚于编代码时的。有了老师的的抛砖引玉,我现在了解了皮毛,以后会更加深入的学习的。

Guess you like

Origin www.cnblogs.com/Villagerr/p/10979471.html