個人のブログでは15週が大きな仕事(アンドロイドスタジオ)で、最終的なチームに基づいてソフトウェアテストのソフトウェアを導入するために私たちを必要とし、私は私を次れますAndroid上でベースのUI AutomatorのAndroidソフトウェアテスト自動化テストを、選びました簡単な紹介。
1、UiAutomatorプロフィール
Uiautomatorは、Googleが期待に沿ったものである結果の出力での各制御要素の外観をクリックして、一般的な手動テストで自動テストUI用のツールを立ち上げています。例えば、ログイン画面とは、正しいそれぞれ間違ったユーザー名とパスワードを入力してログインし、すべてのエラーメッセージ場合などで見ることができますログインボタンをクリックしてください。
それは次のような利点があります。
1.明確なAPIは、正確な制御及び可撓性を提供します。
2.無ソースコードを、
図3は、アプリケーション間で異なるでスケジューリングすることができます。
4. UIAutomatorサポートスクリプト言語は、Javaで、Javaはオブジェクト指向、非常に広い普遍的な言語です。
UIAutomator機能テストテストプロセス:
2、UiAutomator準備
統合されたUI Automatorのは、あなたが最初のプロジェクトは、アプリケーションのGradleのテストに依存してきたことを確認する必要があります。その後のGradleに頼るに以下を追加します。
3、UI要素が見えます
Androidの開発では、あなたは親切findViewByIdの適切なコントロールを見つけることができますが、UI Automatorにも同じように使用している場合、それは多くを依存し、インタフェースをテストするために使用されます。UI Automatorの要素を見つけるために、同様のアプローチを使用しています。UI Automatorのは、テキスト要素、ヒント、contentDescriptionおよびその他のプロパティに基づいて探している、UI Automatorのは、より多くのプロパティ値を取得できるようにするために、UI Automatorのは、また、これらの制御要素にアクセスすることができますしながら、UIインターフェイスは、アクセスされたテスト。
あなたは、属性情報に基づいて対応する制御要素を見つけることができ、その後UI Automatorを、各要素のインターフェイス要素と属性の階層を取得するには、uiautomatorviewer使用することができます。
取得ステップ:
*アプリケーションの起動デバイス上の
*開発マシンにデバイスを接続
*オープンのAndroid SDKディレクトリ、<アンドロイド-SDK> /ツール /。
* uiautomatorviewerが起動し
、その後uiautomatorviewer操作できる
インターフェースは界面構造の現在のレイアウトがあり、右の画面のスナップショットを取得するには、数分待つと属性情報を、*デバイスのスクリーンショットをクリックします。
UIにアクセスできることを確認してください
上で紹介し、UI Automatorのは、テキスト要素に基づいているヒント、contentDescriptionおよびその他のプロパティが探していた、我々は、レイアウト要素として上記の属性に追加する必要があります。時々 、プログラマはその後、通常の使用を確保するために、AccessibilityNodeProviderを実現してください、いくつかのコントロールをカスタマイズする必要があります。
4、試験の例を用いてUiAutomator
プロジェクトは、アプリケーションの互換性テストのために少なくとも100回の携帯電話である必要があり、最初に考えたのは、同じ操作ステーションが操作を繰り返したくない、操作を自動化することです。
基本的な要件は、アップ、試験されているアプリケーションをインストールし、終了し、対応するポップ傍受があるかどうかを検出するために、試験サンプルをインストールすることです。
ここでは基本的なテストプロセスがあります
#ADBは環境変数を設定する必要があり
、いくつかの携帯電話を決定するために#1を
#2にして、どのように多くのアプリケーションを決定
mkiller起動し、mkillerをインストールするには、#3
#4を再インストールし、テストサンプル
のインストールをキャンセルすることがある場合は#5チェックボタンが表示され、によってテストし、テストが失敗したようには見えなかったことが表示されます
オートメーションの使用に関しては、私たちが実行している一人として電話をテストすることはできませんし、同時に複数の携帯電話を実行することができるはずです、私の考えでは、携帯電話を実行するために、各スレッドを実行するために、複数のスレッドを有効にすることです
OK、いくつかの携帯電話があります。
デフfinddevices():
RST = util.exccmd( 'ADBデバイス')
デバイス= re.findall(R '(。*?)\ sの+デバイス'、RST)
lenの場合(デバイス)> 1:
deviceIdsが=装置[1:]
logger.info( '合計%sの携帯電話は' %STR(LEN(デバイス)-1))
deviceIdsで私のために:
logger.info(%のI 'IDは%sでした')
deviceIdsを返します
他:
logger.error(「チェックし、携帯電話を見つけることができませんでした」)
リターン
ここでは非常に明確に書かれたreadme.mdの事実githubの中のpythonでuiautomatorの使用は、述べていますが、実際にはまだいくつかの問題を抱えています
uiautomator使用して単一の電話を通じて、Dオブジェクトを初期化しなければならないとき
複数の電話機することができます
(シリアル)オブジェクトデバイスをD = D初期化し、以降の操作は基本的に、操作は、オブジェクトは、Dは、携帯電話に各考えに対応する、dは
基本的なクリック
#を押してホームキー
d.press.home()
#を押してバックキー
d.press.back()
#キーを押してバックする通常の方法
d.press( "バック")
上のMETA ALTと#を押してキーコードは0x07( '0')(0×02)
d.press(0x07に、0×02)
まず、ADBは、ADBシェル午前startコマンドを使用して起動するコマンドをインストールし使用してインストールし、アプリケーションのインストールを開始します
電話救急箱起動可能-活動は「com.qihoo.mkiller.ui.index.AppEnterActivity」、あなたは、「同意して使う」をクリックして、ユーザーにポップアップ表示されますプロトコルを使用して最初に起動したときです
私はここで使用される方法を監視し、クリックするウォッチャーは、基本的なウォッチャーであります
d.watcher( '同意')。とき(テキスト= U 'と使用することに同意します').click(テキスト= U 'と使用することに同意します')
ウォッチャーは、条件は、ライトの内側に、私がここに持っているときだけで、テキストの同意と利用」であるとき、これらの条件が満たされたときに、ライトバック操作が行われたときに、私はここにいます(クリックしたテキストであることを、私はここに呼ば同意され始め、名前を与えます= U「同意し、使用」)、私は時間のウォッチャーを書く前に、直接クリックする書き込みピットは()私は以前見つかったデフォルトの要素をクリックすると思いますが、後でこれが受け入れられないことがわかっているの内容を書いていないがあり、あなたは、どのオブジェクトをクリックして書き込む必要があります
実際には、これが唯一のウォッチャーを書くことができないビューに一度表示されるためにあなたが書くことができる場所に直接D(テキスト= U「同意し、使用」).click()が、画面が表示される前に、いくつかの遅延があるだろう考えると、携帯電話のすべての種類異なるパフォーマンスではなく、良いプラスtime.sleep()の時間は、私は中に書かれたこの環視のようにすべてをお勧めしますし、それが表示されたらクリックしたとき。
このアプリケーションは、root権限なので、時々、サードパーティのルートツールは、対応する認証プロンプトボックスを再生されますが要求されますので、私は、ルートツールのほとんどは、すべて「許可」ボタンを持つべきだと思うので、私はウォッチャーを追加しました
d.watcher( 'allowroot')。とき(テキストは= uは '許可').click(テキスト= U '許可')
スーパーモデルの演奏ボックスをプレイ再び開きます同意をクリックした後、私はキャンセルしたいこちらをクリック
d.watcher( 'キャンセル')。とき(テキスト= uは 'キャンセル')(uは 'キャンセル' =テキスト).click
之后要点击一下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等等,但是这次要测试的是安卓,虽然这学期有安卓课程的安排,但做测试我还觉得有一些困难的,通过问问同学以及查阅资料才最终解决了我的一些问题。
通过这次的作业,加上前几次的作业,从原型设计工具、源代码管理工具的学习到这次的软件测试环节,我体会到了这门课程的重要性,明白了真正成功的一款软件在真正发布之前做测试所耗费的经历不亚于编代码时的。有了老师的的抛砖引玉,我现在了解了皮毛,以后会更加深入的学习的。