与Monkeyrunner初接触-基本测试

 今年接触了android自动化,主要用Monkeyrunner做基本测试和接口测试,实现手机基本操作,例如安装、删除、登陆、通话等。首先使用Monkeyrunner需要JavaJDK和Python环境,下载androidSDK,使用hierarchyviewer或monitor观察手机键值,然后使用Python脚本进行控制。需要注意的是SDK只能观察工程机的键值,如果想要使用就必须刷机,或者可以学习 http://blog.apkudo.com/tag/viewserver/中的方法,本人没有亲测过。我们在网上找到了wrapeasymonkey.jar包放到了SDK/tools/lib中引用其中比较好用的方法。

 编写Monkeyrunner脚本实现基本测试是很简单的一件事,主要是保证逻辑正确、结构完整,可以在错误时正确提示、截图、抓log。首先一个Python脚本如果想用Monkeyrunner编译需要加上;sys.path.append(os.path.dirname(__file__).encode('utf-8')) 是因为Python的path与monkeyrunner的path并不一致,加上这句后就可以将当前路径加入Monkeyrunner的路径里,转码格式跟别的一致就行,否则Monkeyrunner无法识别,而Python是无法识别这句话的。Jython无法识别中文,不知道有没有解决办法。还有注意Python极其无解的空格分层制,让不同编译器执行同一脚本可能出现Tab空格不一致的错误。

当我刚开始编写测试脚本时,主要是学习先人的思路,将脚本大概分为四个部分:

1)初始化(init)用到的值、键值、变量、方法
2)连接设备(connection)
3)失败时重试(reset)
4)具体方法(main)。

 脚本中主要就是使用“try...except”,“if..else”语句。理解起来也很简单,在你写一个单独的被重复使用的模块时,这上面的结构中就是reset和connection,你要使用try...except来保证找到错误抛出异常,程序能正确运行下去,不会因为突发事件、断网等崩溃。而在Python中和Java不一样,并不需要每个方法都抛出异常,这个也看个人理解和习惯。而if..else就是进行各种判断,保证程序执行错误时能找出错误并记录下来。

 我们在编写时把经常用到的键值和地址等放到了一个配置文件里:我们叫Monkeyrunner.ini,方便改的时候改一个文件就可以实现全部键值都改变。

 在一个脚本中除了通常要引用的wrapeasymonkey和monkeyrunner中的各种方法,我们还引用了自己编写的common和opconfig来方便引用经常使用的方法


 像是在common里的getViewNodeById方法,就是利用键值获取节点信息,主要是因为wrapeasymonkey提供的getview方法获得的view信息不完整,虽然可以根据view进行点击,但是不能通过view获取节点信息,所以我们在common方法中自己写了了获取viewnode方法,它可以提供更多的信息,不仅可以点而且可以获得这个节点的位置信息,通过这个节点找到子节点。

    def getViewNodeById(self,wrapEasyMonkey,Id):
        self.debug('getViewNode: [%s] ' % Id)
        for tmp in range(self.TRYAGAIN):
            try:
                hierarchyViewer = wrapEasyMonkey.device.getHierarchyViewer()
                viewnode = hierarchyViewer.findViewById(Id)
                return viewnode
            except:
                self.debug('getViewNode: the %dst get [%s] error , will retry ' % ( tmp + 1 , Id))
                mr.sleep(1)
                continue
        self.error('getViewNode: [%s] error occured' % Id)
        sys.exc_info()
        traceback.print_exc()
        return None

 而在opconfig中我们主要提供获取文件信息的方法,用来读取键值、更改配置信息等。这里我们经常用的就是getParam方法,就是很简单的根据配置文件的路径和关键词来获得配置文件中的键值或者其他信息。

    def getParam(self,configfile,group,key):
        self.config.read(configfile)
        context = self.config.get(group, key)
        return context


 在后期,我们把脚本中的reset、connection和一些重复用到的方法分别封装了起来,需要用的时候直接引用就可以了。将来我们会把结果放到数据库中,根据Django传送到网页上方便人们查阅。

 记住在运行脚本和运行完成时要让人们知道运行的次数和失败的次数。还要记得一点,你的Monkeyrunner不是为了正常跑起来而写的,最终目的是为了发现BUG并记录。

记录一下我在编写时遇到的一些小问题:

 我们引用wrapeasymonkey中的device方法时产生了无法长时间运行的问题,因为通过wrapeasymonkey获取图层后一直没有释放连接,链接过多后adb就会reject连接,提示信息是can't bind socket。后来改为connection时只使用一次wrapeasymonkey的device方法,而后改用直接使用hierarchy获取图层使monkeyrunner可以多次运行。

 在获APP中的节点时有时会和APP的历史记录、自动显示冲突,需要获取节点的文字甚至图片进行对比才能获得正确结果,这里说一下尽量不要对比图片,与自动化测试便捷准确的准则不符。

 在进行点击动作时常常会发生键值正确却点不到按键的情况,这时要避免用坐标来点,这是下策,要保证自动化脚本的通用性,在我的编写过程中用过先人编写common中的getChildViewNode方法,是通过爷爷节点来定位,也就是子节点的parent的parent。当爷爷节点也错位时,可以通过爸爸节点,也就是一个parent来定位尝试。

 有时是因为弹出框计算错误或者是因为弹出键盘之类的导致坐标改变,需要根据不同情况处理。这里遇到现有方法无法解决时就要创新方法,根据你的具体状况研究出应对策略。

 而我感觉出错最多的就是种种弹出框,各种APP的弹出框情况都不一样,使用wrapeasymonkey中的touchDialogButton方法不一定能解决问题,经常会出现多算一个框,弹出框坐标不是绝对坐标之类的问题。这种情况下最好把各个点的坐标都打出来,思路会清晰很多。

最后是我写的第一个划屏代码片段,希望可以互相学习交流。

# -*- coding: utf-8 -*-
import time,sys,os,re
sys.path.append(os.path.dirname(__file__).encode('utf-8'))
import traceback
import random
from common import *
from OpConfig import OpConfig
from com.whoistester.android.viewclient import ViewClient
from com.whoistester.android.wrapEasyMonkey import wrapEasyMonkey
from com.android.monkeyrunner import MonkeyRunner as mr
from com.android.monkeyrunner import MonkeyDevice as md
from com.android.monkeyrunner import MonkeyImage as mi
from com.android.monkeyrunner.easy import EasyMonkeyDevice
from com.android.monkeyrunner.easy import By
from com.android.chimpchat.hierarchyviewer import HierarchyViewer
from com.android.hierarchyviewerlib.models import ViewNode
class precondition:  #创建类

    def __init__(self):  #初始化用到的方法和键值
        self.cm = common() 
        self.opc = OpConfig()
        self.pwd = os.path.dirname(__file__).encode('utf-8')  #获取当前路径
 
    def connection(self):
        if len(self.cm.deviceslist) < 1: #判断:如果连接设备少于一台就报错
            self.logger.error( "there hava no device has been connection!! This test need at least one devices!")
            return None
        try:
            self.wrapdevice1 = wrapEasyMonkey(self.cm.deviceslist[0][0])  #对连接的第一台设备进行初始化
            self.hierarchy1 = self.wrapdevice1.device.getHierarchyViewer() #用hierarchy方法代替dwrapeasymonkey的device方法
            return True
        except:
            sys.exc_info()
            traceback.print_exc()
            return None
    def reset(self):         #在主函数运行前,和每次失败后需要重置时进行的方法,需要有抛出异常
        for tmp in range(3):
            try:   #根据需要,像是重启APP,删除APP,返回特定界面等等。

            except:
                print('reset: the %dst reset error , will retry' % (tmp + 1))
                mr.sleep(1)
                continue
            
        self.logger.error("reset: error occured")
        sys.exc_info()
        traceback.print_exc()
        return None      
        
    def slide(self):
        screen_high = self.wrapdevice1.device.getProperty('display.height')   #进行划屏过程,获取屏幕宽高。根据比例进行划屏。
        screen_width = self.wrapdevice1.device.getProperty('display.width')
        x1 = int(float(screen_width.encode('utf8')) * 0.9185) #0.9185为比例
        x2 = int(float(screen_width.encode('utf8')) * 0.1185) #向左滑
        y = int(float(screen_high.encode('utf8')) * 0.575) #0.575为比例
           for e in range(1,3): #划屏次数
           self.wrapdevice1.device.drag((x1,y), (x2,y),1.0,10) #从“x1拖动到x2”
           self.wrapdevice1.sleep(2)
if __name__ == '__main__':  #调用连接、重置、主方法。
   a = precondition()
   if self.connection() == None:
      return None
   if self.reset() == None:
      return None
   self.slide()


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值