ubuntu下几种Android测试工具

        前段时间导师要对中国app市场(我们选的是百度,其实哪家都一样)上app的通信安全(登录的时候,用户名密码有没有加密)做自动化检测。

        解决的方法就是在emulator里自动登录app,然后用mitmproxy抓捕数据,对数据进行分析。(怎么分析以后再讲,今天先写操控emulator的工具,图片就不放了,反正也没啥人看)。下面都是自己的一家之言,如果有什么摸索的不对的,欢迎指正。

        第一个是google的hierarchyviewer。

hierarchyviewer的优点:

1.给出手机(以下不提及都是只phone或者emulatoer都可以)当前显示屏幕的UI结构。会用一棵树的形式表现出来。你选择不同的节点,会在hierarchyviewer右下角的分区显示这个节点对应屏幕上的哪一块。

2.另外有个模式,能定位你鼠标所指位置的像素坐标。我觉得这个可能对美工很有用吧,没接触过美工的工具,但是这个能自动放大你鼠标所在位置的图片,像素点很明显的一个个方块。

hierarchyviewer的缺点:

1.很卡,我笔记本是外星人的,还是很卡,用起来很不爽。

2.也就是让你看看UI结构,获得信息并不多。也不能对手机做出操作。


         第二个是monkeyrunner

monkeyrunner是Jython(用JAVA实现的python,它是一个Python语言在Java中的完全实现)。但是我们完整的自动检测程序都打算用python来写,所以这个用用也就放弃了,因为我们找到了可以替代它的,python实现的工具。(下面就会讲)。

不过monkeyrunner可以应付很多安卓测试了吧(没有在公司干过,不知道测试岗具体要做啥)。点击,滑动,输入,截屏,monkeyrunner都可以实现。

这是官方文档的地址:http://developer.android.com/tools/help/MonkeyDevice.html

然后有个中文版的小例子,基本功能也有介绍了:http://fengbohaishang.blog.51cto.com/5106297/962705


        第三个是AndroidViewClient

dtmilano大神用python做的,个人觉得比monkeyrunner更强大好用。在我们自己写工具前,主要用的就是它啦。一个python实现的强大的moudle。你可以把它下载下来,在你的代码里作为import。https://github.com/dtmilano/AndroidViewClient

不过它有个问题,UiAutomator limitation。你在使用dump获取当前UI信息的时候,如果太频繁就会崩。所以师兄后来看了androidviewclient的源码,我们自己写了我们需要的库。


        第四个我们自己系的代码

这里我就直接贴代码了。名字起的都是人能看懂的,所以没怎么注释。

import subprocess # class Deivce
import time
import xml.etree.ElementTree as ET

class View(object):
    def __init__(self, d):
        '''
        initalize with a dict obj
        scrollable : bool, text: str
        long_clickable : bool, focused: bool
        checkable: bool, clickable: bool, 
        password: bool, classtype: (class)str e.g. TextView
        index: int, checked: bool
        package: str e.g.com.qiyi.video, selected: bool
        enabled: bool, bounds: ((x1,y1),(x2,y2))
        content_desc: str, resource_id: str e.g. com.qiyi.video:id/xxx
        focusable: bool, naf: (NAF)(optional) bool
        center: (x1, y1) center of bounds
        '''
        self.scrollable = True if d.get('scrollable')=='true' else False
        self.text = d.get('text')
        self.long_clickable = True if d.get('long-clickable')=='true' else False
        self.focused = True if d.get('focused')=='true' else False
        self.checkable = True if d.get('checkable')=='true' else False
        self.clickable = True if d.get('clickable')=='true' else False
        self.password = True if d.get('password')=='true' else False
        self.classtype = (d.get('class')).replace('android.widget.','')
        self.index = int(d.get('index'))
        self.checked = True if d.get('checked')=='true' else False
        self.package = d.get('package')
        self.selected = True if d.get('selected')=='true' else False
        self.enabled = True if d.get('enabled')=='true' else False
        tmp = tuple((d.get('bounds')).strip('[]').split('][')) # ('1,2','3,4')
        pos1, pos2 = tmp[0].split(','), tmp[1].split(',') # ['1','2']
        self.bounds = (int(pos1[0]),int(pos1[1])), (int(pos2[0]),int(pos2[1]))
        self.content_desc = d.get('content-desc')
        self.resource_id = d.get('resource-id')
        self.focusable = True if d.get('focusable')=='true' else False
        self.naf = True if d.get('NAF')=='true' else False

        (x1, y1), (x2, y2) = self.bounds 
        self.center = x1+(x2-x1)/2, y1+(y2-y1)/2

def getDefaultConfig():
    import configparser
    cfg = configparser.SafeConfigParser()
    cfg.read('config.cfg')
    dtype=cfg.get('device','type')
    height = cfg.get(dtype, 'height')
    width = cfg.get(dtype, 'width')
    go2appspos =  tuple((cfg.get(dtype, 'go2appspos')).split(','))
    appspos = tuple((cfg.get(dtype, 'appspos')).split(','))
    welcomepos =  tuple((cfg.get(dtype, 'welcomepos')).split(','))
    default_apps = (cfg.get(dtype, 'apps')).split(',')
    default_pkgs = (cfg.get(dtype, 'pkg')).split(',')
    config = { 
        'height':height,
        'width':width,
        'go2appspos':go2appspos,
        'appspos':appspos,
        'welcomepos':welcomepos,
        'default_apps':default_apps,
        'default_pkgs':default_pkgs
    }   
    return config

class Device(object):
    def __init__(self, sno, config=getDefaultConfig()):
        '''
        ''' 
        self.sno = sno
        self.height =int(config['height'])
        self.width = int(config['width'])
        self.welcomepos = config['welcomepos']
        self.go2appspos = config['go2appspos']
        self.appspos=config['appspos']
        self.default_apps = config['default_apps']
        self.default_pkgs = config['default_pkgs']
        self.adbshellpf = 'adb -s '+self.sno+' shell '
        self.adbpf = 'adb -s '+self.sno+' '


    @staticmethod
    def shell(cmd, runtime):
        res = subprocess.check_output(cmd, shell=True, universal_newlines=True, timeout=runtime)
        return res

    def parseDump2vl(self, fpath):
        '''parse dump to view-list([view,...])''' 
        tree = ET.parse(fpath)
        vl = []
        for e in tree.iter():
            if e.tag == 'hierarchy':
                continue
            vl.append(View(e.attrib))
        return vl
 
    def dump(self, filepath='', savefile=False):
        '''get list of view from current ui
            return [ view, ...]'''
        if filepath == '':
            fpath = '/tmp/trafficgen/'+self.sno+'/uidump.xml'
        else:
            fpath = filepath

        t = 1
        for chance in range(6):
            time.sleep(t-1) # '-1': no wait for first attempt of dump
            try:
                cmd = self.adbshellpf+' uiautomator dump --compressed'
                self.shell(cmd, 10)
                cmd = self.adbpf+' pull /storage/sdcard/window_dump.xml '+fpath
                self.shell(cmd, 10)
            except:
                if(chance == 5):
                    if not savefile:
                        cmd = 'rm '+fpath
                        self.shell(cmd, 10)
                    print('dump failed')
                    raise
                t *= 2
            else:
                break
            finally:
                cmd = self.adbshellpf+' rm /storage/sdcard/window_dump.xml'
                self.shell(cmd, 10)
 
        vl = self.parseDump2vl(fpath)#default: /tmp/trafficgen/sno/dumpui.xml
        if not savefile:
            cmd = 'rm '+fpath
            self.shell(cmd, 10)
        return vl

    def tap(self, pos, waittime=0):
        '''pos: (x, y), sleeptime: sleep time'''
        x, y = pos
        cmd = self.adbshellpf+' input tap '+str(x)+' '+str(y)
        self.shell(cmd, 10)
        if waittime > 0:
            time.sleep(waittime)

    def swipe(self, startpos, endpos, duration=512):
        '''startpos, endpos : (x1,y1),(x2,y2)'''
        x1, y1 = startpos
        x2, y2 = endpos
        x1,y1,x2,y2,duration = str(x1),str(y1),str(x2),str(y2),str(duration)
        cmd = self.adbshellpf+' input swipe '+' '.join((x1,y1,x2,y2,duration))
        self.shell(cmd, 10)

    def swipeLeft(self):
        margin = self.width/10
        w,h = self.width, self.height
        startpos, endpos = (w-margin, h/2),(margin, h/2)
        self.swipe(startpos, endpos, 200)

    def swipeRight(self):
        margin = self.width/10
        w,h = self.width, self.height
        endpos, startpos = (w-margin, h/2),(margin, h/2)
        self.swipe(startpos, endpos, 200)

    def swipeUp(self):
        margin = self.height/5
        w,h = self.width, self.height
        startpos, endpos = (w/2, h - margin), (w/2, margin)
        self.swipe(startpos, endpos, 200)

    def swipeDown(self):
        margin = self.height/5
        w,h = self.width, self.height
        endpos, startpos = (w/2, h - margin), (w/2, margin)
        self.swipe(startpos, endpos, 200)

    def inputText(self, msg):
        '''
        adb shell input text msg
        msg only accept [0-9][a-zA-Z]|@|.
        space no supported
        '''
        cmd = self.adbshellpf+' input text '+msg
        self.shell(cmd, 10)
 
    def screencap(self, filepath='.'):
        '''
        adb shell screencap /storage/sdcard/sc.png
        adb pull /storage/sdcard/sc.png filepath
        filepath: local file path e.g. ./dir/screen.png
        '''
        remotefile = '/storage/sdcard/sc.png'
        cmd = self.adbshellpf+' screencap '+remotefile
        self.shell(cmd, 10)
        cmd = self.adbpf+' pull '+remotefile+' '+filepath
        self.shell(cmd, 10)

    def go2home(self):
        """input keyevent KEYCODE_HOME"""
        cmd = self.adbshellpf+' input keyevent KEYCODE_HOME'
        self.shell(cmd, 10)
        time.sleep(1)
    
    def go2apps(self):
        self.tap(self.go2appspos, 1)

    def installApp(self, fpath):
        cmd = self.adbpf + ' install '+ fpath
        self.shell(cmd, 60)
 
    def getOne3rdPartyApp(self):
        """return 1 user-installed 3rd party package
            return: None | pkg name"""
        cmd = self.adbshellpf + " pm list packages -3 "
        tmp_res = self.shell(cmd, 30)
        tmp_res = tmp_res.split('\r\n')
        for line in list(tmp_res): # x86 img contain WARNING msg
            if ('WARNING' in line) or ('' == line):
                tmp_res.remove(line)
        res = None
        for entry in tmp_res:
            pkg = (entry.split(':'))[1]
            if pkg not in self.default_pkgs:
                res = pkg 
        return res 

    def uninstallApp(self):
        """uninstall the only package pkg 
            do nothing | remove the pkg"""
        pkg_name = self.getOne3rdPartyApp()
        if(pkg_name == None):
            return False
        cmd = self.adbpf + " uninstall " + pkg_name
        self.shell(cmd, 60)
        return True

    def startApp(self):
        '''precondition: at Apps page
        start new installed app'''
        flag = False
        self.tap(self.appspos, 1)
        self.swipeRight()
        for pageindex in range(2):
            vl = self.dump()
            for v in vl:
                if ('TextView' == v.classtype) and \
                    (v.text not in self.default_apps):
                    flag = True
                    self.tap(v.center, 1)
                    return flag
            self.swipeLeft()
        return flag

    def forcestopApp(self):
        """force stop app by pkg-name adb shell am force-stop pkg-name"""
        flag = False
        pkg = self.getOne3rdPartyApp()
        if pkg == None:
            return flag
        cmd = self.adbshellpf + " am force-stop " + pkg
        self.shell(cmd, 10)
        flag = True
        return flag

    def cleanupSdcard(self):
        '''
        rm -r /storage/sdcard/*
        mkdir /storage/sdcard/D1 ...
        '''
        sdir = '/storage/sdcard/'
        dirs1 = ['Alarms','DCIM','Download','LOST.DIR']
        dirs2 = ['Movies','Music','Notifications']
        dirs3 = ['Pictures','Podcasts','Ringtones']
        dirs = dirs1+dirs2+dirs3
        cmd = '; mkdir '
        for d in dirs:
            d = sdir+d+' '
            cmd += d
        cmd = ' rm -r '+sdir+'* '+cmd
        cmd = self.adbshellpf+" '"+cmd+"'"
        self.shell(cmd, 10)

    def skipWelcomeMsg(self):
        self.tap(self.welcomepos, 2)
        self.go2apps()
        self.tap(self.welcomepos, 5)
        self.go2home()

def startadbserv():
    cmd = 'adb  start-server'
    subprocess.check_output(cmd, shell=True, timeout=30)

def stopadbserv():
    cmd = 'adb kill-server'
    subprocess.check_output(cmd, shell=True, timeout=30)
    

if __name__=='__main__':

    sno = 'emulator-5554'
    device = Device(sno)


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值