功能性测试:
- App启动过程中的耗时情况
- CPU占比率
- 流量消耗情况
- 电量消耗情况
- 内存消耗情况
- 流畅度(FPS,就是每秒钟的帧数,流畅度,流畅度通过该指标就可以看到app流畅度异常的情况)
- 过度渲染(流畅度一个方面就是过度渲染)
环境的配置
- android sdk(这个可以去android的官网下载,地址:https://developer.android.com/studio/)
- python2.7
- pycharm(开发的一个ide)
1:android sdk:
这个需要解压,解压完了以后把相应的环境变量添加进去,添加两三个,
- 第一个是sdk的路径,
- platform tools 这个是为了我们待会要使用的adb 命令
- tools,这个是为了待会为了获取到界面元素工具:uiautomatorviewer
校验环境变量是否配置成功通过:adb devices,看有没有设备信息输出
emulator-5554 device
2:python2.7
去python的官网下载(https://www.python.org/downloads/),有两个版本3.5.2和2.7,建议使用2.7,这个两个差异很大甚至都不兼容。
功能性测试:
启动时间
- 冷启动:进程第一次被启动所消耗的时间的过程。启动指令:adb shell am start -W -n package/activity, 停止指令:adb shell am force-stop package,这个跟热启动的停止指令有区别
- 热启动: 是你应用被启动后点击back键或者home键应用程度回到后台进程未被杀死的状态再次启动的过程
1:冷启动
比如此时我现在要测试我们信用猫的启动,通过adb shell am start -W -n package/activity,但是我并不知道信用猫的package和activity,因为再启动之前我们选去获取package和activity,使用adb logcat | grep START,然后点击想要打开的应用,会出现: START u0 {cmp=com.vcredit.creditcat/.creditmodule.start.activity.HomeMainActivity} from pid 18609
这个就是com.vcredit.creditcat包名,.MainActivity就是activity名。
adb shell am start -W -n com.vcredit.creditcat/.creditmodule.start.activity.HomeMainActivity,返回参数
Starting: Intent { cmp=com.vcredit.creditcat/.creditmodule.start.activity.HomeMainActivity }
Status: ok
Activity: com.vcredit.creditcat/.creditmodule.start.activity.HomeMainActivity
ThisTime: 1676
TotalTime: 1676
Complete
status,操作成功状态
Activity:当前的界面
ThisTime就是执行这次命令的耗时,可以把这个时间作为启动app的参考值
关闭app的命令:
adb shell am force-stop com.vcredit.creditcat
如果要是写脚本的话,就要把这个过程脚本话,比如连续开启关闭100次效果耗时操作等等
2:热启动
启动命令跟跟冷启动的命令是一样的,区别是在于退出app使用的指令是不一样的,这里使用的是:adb shell input keyevent 3
这个命令含义就是发送一个event的事件,这里的3就是手机上的back键。
3:自动化脚本获取启动时间的实现
首先时间:1:获取命令执行的时间,作为启动时间的参考值。2:在命令前后加上时间的时间戳,差值作为启动时间的参考值
个人更觉得加上时间戳作为保准会更准一些,因为可能那个命令的返回的参数给我们了,app还是处于一个启动状态
可以通过这些获取到时间的均值和波动情况
参考的话,1:不同厂家的软件的对比,可以使用微信,淘宝等app相关软件的启动时间的参数作为参考,看我们在业界是什么样的水平,2:也可以版本之间的对比,看版本的开发中是否发现启动的时间的延迟
#/usr/bin/python
#encoding:utf-8
import csv
import os
import time
class App(object):
def __init__(self):
self.content = ""
self.startTime = 0
#启动App
def LaunchApp(self):
cmd = 'adb shell am start -W -n com.vcredit.creditcat/.creditmodule.start.activity.HomeMainActivity'
self.content=os.popen(cmd)
#停止App
def StopApp(self):
#cmd = 'adb shell am force-stop com.android.browser'
cmd = 'adb shell input keyevent 3'
os.popen(cmd)
#获取启动时间
def GetLaunchedTime(self):
for line in self.content.readlines():
if "ThisTime" in line:
self.startTime = line.split(":")[1]
break
return self.startTime
#控制类
class Controller(object):
def __init__(self, count):
self.app = App()
self.counter = count
self.alldata = [("timestamp", "elapsedtime")]
#单次测试过程
def testprocess(self):
self.app.LaunchApp()
time.sleep(5)
elpasedtime = self.app.GetLaunchedTime()
self.app.StopApp()
time.sleep(3)
currenttime = self.getCurrentTime()
self.alldata.append((currenttime, elpasedtime))
#多次执行测试过程
def run(self):
while self.counter >0:
self.testprocess()
self.counter = self.counter - 1
#获取当前的时间戳
def getCurrentTime(self):
currentTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
return currentTime
#数据的存储
def SaveDataToCSV(self):
csvfile = file('startTime2.csv', 'wb')
writer = csv.writer(csvfile)
writer.writerows(self.alldata)
csvfile.close()
if __name__ == "__main__":
controller = Controller(10)
controller.run()
controller.SaveDataToCSV()
4:2-7 详解【CPU】监控值的获取方法、脚本实现和数据分析
#/usr/bin/python
#encoding:utf-8
import csv
import os
import time
#控制类
class Controller(object):
def __init__(self, count):
self.counter = count
self.alldata = [("timestamp", "cpustatus")]
#单次测试过程
def testprocess(self):
result = os.popen("adb shell dumpsys cpuinfo | grep com.vcredit.creditcat")
for line in result.readlines():
cpuvalue = line.split("%")[0]
currenttime = self.getCurrentTime()
self.alldata.append((currenttime, cpuvalue))
#多次执行测试过程
def run(self):
while self.counter >0:
self.testprocess()
self.counter = self.counter - 1
time.sleep(3)
#获取当前的时间戳
def getCurrentTime(self):
currentTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
return currentTime
#数据的存储
def SaveDataToCSV(self):
csvfile = file('cpustatus.csv', 'wb')
writer = csv.writer(csvfile)
writer.writerows(self.alldata)
csvfile.close()
if __name__ == "__main__":
controller = Controller(10)
controller.run()
controller.SaveDataToCSV()
5:2-8 详解【流量】监控值的获取方法
获取流量是我们app为我们用户节省流量也是一个很重要的维度,如何获取流量呢,首先要获取进程的id,指令:adb shell ps | grep com.vcredit.creditcat
u0_a55 19287 1 1352 124 c02caffc b76ed610 S /data/data/com.vcredit.creditcat/files/DaemonServer
u0_a55 19398 1134 871988 198048 ffffffff b76e2f1b S com.vcredit.creditcat
u0_a55 19490 1134 754824 38636 ffffffff b76e2f1b S com.vcredit.creditcat:channel
可以看出我们的进程id是19287,后面两个分别是渠道和百度的推送的进程
获取到流量以后我们可以通过进程工具获取到流量:adb shell cat /proc/pid/net/dev
这里就是:adb shell cat /proc/19287/net/dev
zewdeMacBook-Pro:~ zew$ adb shell cat /proc/19287/net/dev
Inter-| Receive | Transmit
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
sit0: 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
lo: 37027 526 0 0 0 0 0 0 37027 526 0 0 0 0 0 0
eth0: 16355484 19133 0 0 0 0 0 0 2190564 15511 0 0 0 0 0 0
这里我们只需要关注receive,和transmit,receive代表接受到的数据,transmit发表发出请求的数据,流量就是这两个之和
底下的lo代表localhost,指的是本地的流量,不用统计,eth0,eth1代表有两个网卡,两个网卡都会有流量的输出,所以也要统计,我们就是通过开始计算之前的流量值,再统计进行一系列操作后的流量值,做一个差值,最后差值就是我们这段时间操作的流量的消耗情况。
至于要测试多少次能,比如我们假如测试10分钟,那就是10*60/5,因为我们中间有五秒中的停留。算出来整个测试过程中执行的次数
#/usr/bin/python
#encoding:utf-8
import csv
import os
import string
import time
#控制类
class Controller(object):
def __init__(self, count):
#定义测试的次数
self.counter = count
#定义收集数据的数组
self.alldata = [("timestamp", "traffic")]
#单次测试过程
def testprocess(self):
#执行获取进程的命令
result = os.popen("adb shell ps | grep com.vcredit.creditcat")
#获取进程ID
result3="#".join(result.readlines()[0].split())
pid = result3.split("#")[1]
#获取进程ID使用的流量
traffic = os.popen("adb shell cat /proc/"+pid+"/net/dev")
for line in traffic:
if "eth0" in line:
#将所有空行换成#
line = "#".join(line.split())
#按#号拆分,获取收到和发出的流量
receive = line.split("#")[1]
transmit = line.split("#")[9]
elif "eth1" in line:
# 将所有空行换成#
line = "#".join(line.split())
# 按#号拆分,获取收到和发出的流量
receive2 = line.split("#")[1]
transmit2 = line.split("#")[9]
#计算所有流量的之和
# alltraffic = string .atoi(receive) + string .atoi(transmit) + string .atoi(receive2) + string .atoi(transmit2)
alltraffic = string .atoi(receive) + string .atoi(transmit)
#按KB计算流量值
alltraffic = alltraffic/1024
#获取当前时间
currenttime = self.getCurrentTime()
#将获取到的数据存到数组中
self.alldata.append((currenttime, alltraffic))
#多次测试过程控制
def run(self):
while self.counter >0:
self.testprocess()
self.counter = self.counter - 1
#每5秒钟采集一次数据
time.sleep(5)
#获取当前的时间戳
def getCurrentTime(self):
currentTime = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
return currentTime
#数据的存储
def SaveDataToCSV(self):
csvfile = file('traffic.csv', 'wb')
writer = csv.writer(csvfile)
writer.writerows(self.alldata)
csvfile.close()
if __name__ == "__main__":
controller = Controller(5)
controller.run()
controller.SaveDataToCSV()
6:详解【电量】监控值的获取方法
我们都知道智能机在使用过程中电量是一个很大的软肋,我们在使用过程中使用手机的硬件来测试电量是非常准确的,而我们现在是通过命令:adb shell dumpsys battery,这个跟硬件获取到电量会有差距,但是作为一家创业公司,没有必要通过一个硬件设施去测试电量。测试电量的时候我们用电脑和手机设备连接的时候手机会进入一个充电状态,所以我们必须保证手机是一个非充电的状态,那么如何切换非充电状态,可以执行命令:adb shell dumpsys battery set status 1,这个命令就是让手机进入非充电状态,其实只要是非2就可以,2代表的就是充电状态,效果
adb shell dumpsys battery
Current Battery Service state:
(UPDATES STOPPED -- use 'reset' to restart)
AC powered: true
USB powered: false
Wireless powered: false
status: 2
health: 2
present: true
level: 100
scale: 100
voltage: 0
temperature: 0
technology: Li-ion
level值就是当前的电量,所以我们测试的时候只需要关注这个level值就行了。只需要在测试中拿最后的level值与最初的level值做差就能获取到消耗电量的情况。
timestamp,power
2018-08-22 17:33:35," 100
"
2018-08-22 17:33:40," 100
"
2018-08-22 17:33:45," 100
"
2018-08-22 17:33:50," 100
"
2018-08-22 17:33:55," 100
"
因为我们使用的非常短,所以没有出现大的变化。真是情况中,要设置使用半个小时或者一个小时,电量的消耗情况,如果特别短其实是看不到电量的消耗的差异的。
7:详解【内存】监控值的获取方法
命令是:adb shell top ,会列出当前系统的所有的进程以及相关的信息,这些信息里就包含了相关的内存信息。内存需要存两个值,一个是虚存,一个是实存。
VSS:虚拟耗用内存
RSS:实际使用物理内存
如果在使用过程中我们的内存一直处于一个恒定,稳定的状态说明我们的app没有出现内存泄漏的情况。
PID PR CPU% S #THR VSS RSS PCY UID Name
23467 0 0% S 58 827244K 135172K bg u0_a55 com.vcredit.creditcat
1819 0 0% S 3 4056K 448K fg media_rw /system/bin/sdcard
1145 1 0% S 6 6336K 312K fg root /sbin/adbd
1638 1 0% S 57 835340K 116612K fg system system_server
1115 0 0% S 1 0K 0K fg root kworker/0:1H
18767 0 0% S 1 1800K 928K fg root logcat
19846 0 0% S 38 781896K 62084K bg u0_a14 com.android.browser
9 0 0% S 1 0K 0K fg root rcu_bh
10 1 0% S 1 0K 0K fg root rcu_sched
11 1 0% S 1 0K 0K unk root migration/1
12 1 0% S 1 0K 0K fg root ksoftirqd/1
14 1 0% S 1 0K 0K fg root kworker/1:0H
15 1 0
第一列:PID是所有进程的ID,第三列是CPU的使用率,VSS是虚存,RSS是实存,name就是我们程序的名称
看这个第一列就是我们的进程:VSS:827244K,RSS:135172K
我们就是去获取两个值的和,定期的去取值,然后做成曲线图的方式。
命令:adb shell top -d 1 >meminfo,代表的是这个命令会一秒钟刷新一次,我们把这些数据获取到放到文件里保存。
然后查看meminfo文件里包含了所有的数据文件,查看的命令是:cat meminfo
然后通过命令:cat meminfo | grep com.vcredit.creditcat进行过滤。
这一个合适的参考值取一个经验值,我们测试不止一次,每一轮都会有一个内存参考值,根据这个参考值可以确定内存的变化是不是在一个合理的范围内。
8 详解【FPS&过度渲染】的概念和监控方法 - 分析页面卡慢的方法
- FPS:每秒钟的帧数。在安卓系统定义为一秒钟60帧定义为很流畅,以为一帧得16毫秒,如果帧的时间大于16毫秒,我们就可以认为有卡顿出现,如果看到呢。我们是在手机里相关的设置是可以看到的。找到你的开发者选项,找到GPU呈现模式分析(Profile GPU rendering),选中“在屏幕显示为条形图”选项,在屏幕中会看到很多条形线,有个绿色的线就是FPS的基准值16毫秒,没一个柱形图就是每一帧的绘图的耗时,如果这一帧的耗时超过16毫秒,你就会看到绘制的图大于那个基准值,如果小于就会在绿色的线以下,如果你发现很多的柱形图都在绿线以上说明是有卡顿的。
- 过度渲染:描述的是屏幕上的某一像素在同一帧的时间内被绘制了多次。在开发者选项中找到显示GPU过度绘制,然后就会在你的安卓系统上看到一些变化,颜色越深代表当前地方被绘制的次数越多,如果你发现你的页面非常卡的时候可以打开过度绘制把一份元素绘制过多导致了页面的响应过慢。
Android App自动化测试框架的应用
- 代码比较混乱
- 重复编码,效率低
- 需求变化,难维护,每个版本我们的测试用例都得进行更换,如果没有一个好的框架帮我们的话会导致测试维护成本很高。
为了解决上面3个问题,所以我们使用框架来帮我们解决
应用测试框架的意义
- 提高代码的易读性
- 提高代码的效率
- 提高代码的易维护性
- 自动化实例
- Unittest
- 数据驱动(大批量数据,降低冗余率)
- 实践
Test Fixture 包含了三个步骤,setUp(),testcase(),teardown(),三个步骤。且是按照顺序来,执行的,setUp做一些初始化的动作,tearDown做一些资源释放的操作,中间的test_something就是不同的测试用例,好处在于我们只需要实现一次就可以在所有的测试用例中通用,节省了代码的编写,提高了代码的易维护性。
class MyTestCase(unittest.TestCase):
def setUp(self):
print "setUp"
def tearDown(self):
print "tearDown"
def test_something(self):
print "test_something"
if __name__ == '__main__':
unittest.main()
打印结果
setUp
test_something
tearDown
那么如果是不同的测试用例,比如再加一个测试用例test_something2,我们期望的是顺序是 setUP test_something test_something2 teardown.
class MyTestCase(unittest.TestCase):
def setUp(self):
print "setUp"
def tearDown(self):
print "tearDown"
def test_something(self):
print "test_something"
def test_something2(self):
print "test_something2"
if __name__ == '__main__':
unittest.main()
setUp
test_something
----------------------------------------------------------------------
tearDown
setUp
test_something2
tearDown
大家应该清楚测试用例setUp,tearDown的用处了吧
Test Case
就是具体的测试用例
Test Suite
下面介绍下什么是Test Suite ,就是一个集合,可以控制一组测试用例的执行,为什么要用一个集合来控制测试用例的执行呢,因为有的时候我们需要某一些测试用例来完成,我们写了一百条,只想跑一些重要功能的测试用例,这时候我们可以声明一个这样的集合,集合了加我们要跑的测试用例,一起执行
Test Runner
他是用来执行测试用例的,他会给我们提供一个测试用例结果的输出,
那么接下来我们用Test Case Test Suite Test Runner配合使用完成运行测试脚本
import UnitTestDemo1
import unittest
mysuite = unittest.TestSuite()
mysuite.addTest(UnitTestDemo1.MyTestCase("test_something"))
mysuite.addTest(UnitTestDemo1.MyTestCase("test_something2"))
mytestrunner = unittest.TextTestRunner(verbosity=2)
mytestrunner.run(mysuite)
打印日志
setUp
test_something
tearDown
setUptest_something2
tearDown
如果你不想测试test_something2用例,只需要给这个注释掉就可以了。有时候我们又是希望是以一个类的方式加载执行里面的所有的用例那么怎么实现呢?
import UnitTestDemo1
import unittest
# mysuite = unittest.TestSuite()
# mysuite.addTest(UnitTestDemo1.MyTestCase("test_something"))
# mysuite.addTest(UnitTestDemo1.MyTestCase("test_something2"))
cases=unittest.TestLoader().loadTestsFromTestCase(UnitTestDemo1.MyTestCase)
mysuite = unittest.TestSuite([cases])
mytestrunner = unittest.TextTestRunner(verbosity=2)
mytestrunner.run(mysuite)
这样就把MyTestCase里的测试用例都可以加进去了,那么如果我们希望测试一个类的所有测试用例和另外一个类的部分测试用例,该怎么做呢?如下图,我们应该是把MyTestCase类中的test_something和test_something2执行一遍以后再添加一个test_something用例执行一次。
import UnitTestDemo1
import unittest
# mysuite = unittest.TestSuite()
# mysuite.addTest(UnitTestDemo1.MyTestCase("test_something"))
# mysuite.addTest(UnitTestDemo1.MyTestCase("test_something2"))
# cases=unittest.TestLoader().loadTestsFromTestCase(UnitTestDemo1.MyTestCase)
# mysuite = unittest.TestSuite([cases])
cases=unittest.TestLoader().loadTestsFromTestCase(UnitTestDemo1.MyTestCase)
mysuite = unittest.TestSuite([cases])
mysuite.addTest(UnitTestDemo1.MyTestCase("test_something"))
mytestrunner = unittest.TextTestRunner(verbosity=2)
mytestrunner.run(mysuite)
打印结果如下:
setUp
test_something
tearDown
setUp
test_something2
tearDown
----------------------------------------------------------------------
setUp
test_something
tearDown
OK
数据驱动DDT
针对同一个测试用例可能需要不同的数据源,使用DDT不需要重复造轮子,提高代码的整洁度,也不需要复杂的读取数据文件的过程,还是代码的效率很高。
- 准备第三方库,首先安装ddt库,其次在脚本中引入ddt(https://pypi.org/project/ddt/#files)然后解压到对应的没有了,找到setup.py,拖拽到命令行里执行命令:sudo python /Users/zew/Public/ddt-1.2.0/setup.py install 。那幢完成以后再项目中使用from ddt import ddt, data, unpack 引入ddt的库
zewdeMacBook-Pro:~ zew$ sudo python /Users/zew/Public/ddt-1.2.0/setup.py install
Password:
running install
Checking .pth file support in /Library/Python/2.7/site-packages/
/usr/bin/python -E -c pass
TEST PASSED: /Library/Python/2.7/site-packages/ appears to support .pth files
running bdist_egg
running egg_info
creating ddt.egg-info
writing ddt.egg-info/PKG-INFO
writing top-level names to ddt.egg-info/top_level.txt
writing dependency_links to ddt.egg-info/dependency_links.txt
writing manifest file 'ddt.egg-info/SOURCES.txt'
warning: manifest_maker: standard file 'setup.py' not found
file ddt.py (for module ddt) not found
reading manifest file 'ddt.egg-info/SOURCES.txt'
writing manifest file 'ddt.egg-info/SOURCES.txt'
installing library code to build/bdist.macosx-10.11-intel/egg
running install_lib
running build_py
file ddt.py (for module ddt) not found
file ddt.py (for module ddt) not found
warning: install_lib: 'build/lib' does not exist -- no Python modules to install
creating build
creating build/bdist.macosx-10.11-intel
creating build/bdist.macosx-10.11-intel/egg
creating build/bdist.macosx-10.11-intel/egg/EGG-INFO
copying ddt.egg-info/PKG-INFO -> build/bdist.macosx-10.11-intel/egg/EGG-INFO
copying ddt.egg-info/SOURCES.txt -> build/bdist.macosx-10.11-intel/egg/EGG-INFO
copying ddt.egg-info/dependency_links.txt -> build/bdist.macosx-10.11-intel/egg/EGG-INFO
copying ddt.egg-info/top_level.txt -> build/bdist.macosx-10.11-intel/egg/EGG-INFO
zip_safe flag not set; analyzing archive contents...
creating dist
creating 'dist/ddt-1.2.0-py2.7.egg' and adding 'build/bdist.macosx-10.11-intel/egg' to it
removing 'build/bdist.macosx-10.11-intel/egg' (and everything under it)
Processing ddt-1.2.0-py2.7.egg
Copying ddt-1.2.0-py2.7.egg to /Library/Python/2.7/site-packages
Adding ddt 1.2.0 to easy-install.pth file
Installed /Library/Python/2.7/site-packages/ddt-1.2.0-py2.7.egg
Processing dependencies for ddt==1.2.0
Finished processing dependencies for ddt==1.2.0
当出现这个的时候说明ddt安装成功了,如何使用呢
- 首先在类前面加个修饰,说明本次的测试用例使用的是ddt测试框架
@ddt
class LoginCase(MyTestCase):
- 在测试用例的方法前加上,这里的还分为有一个参数,和多个参数,如何是一个参数,只需要在@data修饰,在括号里加上测试的参数值
@data("233","234","54545)
@unpack
def testLogin(self, phonenum):
如果是多个参数,两个或者两个以上的话,也是用@data修饰,再加上一个@unpack,告诉我们测试用例是有多个参数的。
@data(("18717939742", "233"))
@unpack
def testLogin(self, phonenum, vertify):
Appium框架的介绍
- 自动化工具的介绍
- 环境的准备
- 元素识别工具
- 脚本的设计原则
- 自动化脚本的实现
- 相关api的应用
1:自动化工具的介绍
无论哪种测试工具都必须是安卓平台提供的,否则也无法实现对手机app这一层的控制的,市面上的很多测试工具,robotium,appium,他们其实都是对系统平台已有的测试框架进行了一次封装。比如安卓有的uiautomator,他是基于java语言的测试,那我们今天要说的是appium,那么他们是什么关系呢。关系图如下
python--------------->appium(python)-------------->android uiaotumator--------->手机app
作为测试工程师不需要关心appium是怎么控制uiaotumator的,因为它内部已经做好了封装,我们只需要去使用的。
2:环境的准备
- appium,官网:http://appium.io,它是一个开源的,跨平台的自动化测试工具,用于测试Native和Hybrid应用,支持ios,android 和FirefoxOs平台,在不同平台,我们的appium是基于不同的框架,比如android平台它是基于UIAutomator框架
- Test device
- Test app
- Appium-python-Client,Selenium(appium库是集成Selenium)
Appium理念:
1:无需重新编译应用(因为有的instrumtation自动化的过程就必须的源码,将源码编译生成过程测试。这样导致既要维护自己的脚本也要维护开发的代码,测试很麻烦)
2:不局限于语言和框架(java,c++,是基于任何语言的)
3:无需重复造轮子,接口统一
4:开源
特点:
跨架构,native,hybrid,webview
跨设备,安卓,ios firefox os
跨语言:java ,Python,ruby,php,JavaScript
跨进程:不依赖源码(基于uiautomator)
Appium的整个操作流程:手机里有个BootStrap.jar,和UiAutomator command server,我们app的自动化是使用UiAutomator来实现的
那么就需要UiAutomator来控制我们app,为了实现我们控制app,我们就需要把UiAutomator各种api进行封装,这个封装好的驱动程序就叫做BootStrap.jar,因此我们手机里必须得有一个BootStrap.jar文件和启动一个server(UiAutomator command server)这个server是一个TCPserver,作用主要是用来接收appium发送过来的各种自动化命令,这个server的启动必须得依赖BootStrap.jar文件,手机本身是不存在这个BootStrap.jar驱动程序,那么来自哪里呢?是来自于我们的appium框架,appium两个部分,上面是UiAutomator controller,下面是UiAutomator command client ,UiAutomator controller作用是将Appium自带的这个BootStrap.jar文件从pc端传送到手机上,是通过adb命令推送到我们手机,推送到以后执行一个指令启动BootStrap.jar,进而会监听一个端口号,进来开启了Server的服务的功能,启动完以后我们需要appium发送各种指令,这个指令就是来自于UiAutomator command client。它将各种指令发送到手机的TCP server 进入控制我们的app,那么我们的appium的各种指令又是来自于哪里呢,就是来自我们的WebDriver Script,来自我们的脚本把指令发送给appium,我们appium将指令发送给手机的UiAutomator command server,然后进而通过UiAutomator command server控制我们的app。UiAutomator controller主要是帮助我们实现自动化测试开始时候的环境初始化。所有自动化基本的执行就是我们的UiAutomator command client实现的。
3:元素识别工具
通过android sdk的工具里的uiautomatorviewer来识别页面的元素
/Users/zew/Library/Android/sdk/tools/bin
./uiautomatorviewer 启动起来
这玩意的作用是捕捉页面上的各个元素并形成一个元素的文档树
初始化的相关配置
desired_caps = {}
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '4.4'
desired_caps['deviceName'] = 'emulator-5554'
# desired_caps['appPackage'] = 'com.android.calculator2'
desired_caps['appPackage'] = 'com.vcredit.creditcat'
# desired_caps['appActivity'] = '.Calculator'
desired_caps['appActivity'] = '.creditmodule.start.activity.HomeMainActivity'
desired_caps["unicodeKeyboard"] = "True"
desired_caps["resetKeyboard"] = "True"
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
这个deviceName如何获取呢,打开终端,输入 adb devices
zewdeMacBook-Pro:~ zew$ adb devices
List of devices attached
emulator-5554 device
MVSWP7AAKNONBAS4 device
desired_caps['appPackage'] = 'com.example.zhangjian.minibrowser2'
如何获取appPackage呢,
adb logcat | grep START
desired_caps["unicodeKeyboard"] = “True"
支出我们输入中文就支持了,否则可能无法输入或者乱码的
desired_caps["resetKeyboard"] = "True"
这个resetkeyboard作业就是测试完脚本以后进行的键盘进行恢复,如果为false就是脚本执行完舒服发没有被设置回去
self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
获取到应用程序的操作句柄
如何获取呢?
第一个参数,启动的uri,第二个参数就是刚刚的那个参数
这个uri就是appuim启动时的服务的ip和端口号
意思就是我们的脚本都会传给这个端口的服务的tcp cline发送到手机
编写脚本遵循love原则
有时候控件的id是没有的,那就通过find_element_by_class_name去获取,通过类名去寻找控件,如果这个界面有很多相同的类名的控件,它首先返回的是第一个控件,如果有多个的话,通过这个并且不是第一个的话通过find_element_by_class_name始终是第一个会导致出现问题,那如何去解决这个问题呢。
可以通过find_elements_by_class_name来取
有时候我们也要去抛出异常为了方便我们去调试程序,让程序更加健壮,我们得收集异常。
相关api的应用
press_keycode
send_keys
scroll() 滚动,两个参数:第一个源原色,目标元素,从哪个控件,滚到那个
drag_and_drop(),屏幕上长按拖拽然后释放的一个过程
tap()点击手机的屏幕,参数是一个数组,也可以多个点
swipe()—-通常会跟swipe up和swipe down使用,向上滑和向下滑
swipe()有四个餐宿,分别是屏幕的起始位置的x,y和终止位置的x,y
flick()也是一个滑动的作用
current_activity()返回当前activity的名字的api
wait_activity(activity, timeout, interval=1)等待当前activity的显示,显示了就回true,没显示就是false,第二个参数就是等待时间,第三个参数是重复几次
background_app()就是将app置于后台,多长时间再回到前台,参数就是一个多少秒回到前台。
close_app()关闭app
start_activity(),启动某个应用的某个activity,参数一个是包名,一个是activity名get_screenshot_as_file()进行截屏,如果刚刚打开界面就截屏有可能图片会为空白,因为刚刚打开界面界面没有展示完就截的话会有白屏的可能
Appium测试Hybrid
测试Hybrid的应用程序我们区别于原生而是使用的是Selendroid框架,Selendroid本身也是基于java语言开发的,因为Instrumtation也是基于java语言开发的,那么如何驱动python驱动Selendroid的呢,那是使用Appium驱动Selendroid进而控制我们手机上的app。也就是说Appium的强大之处在于将所有的外部的测试框架进行的柔和,暴露出统一的接口给外部进行使用。
- 针对Hybrid的App,我们Appium基于Selendroid框架的实现,而Selendroid框架是基于Instrumention框架实现的
- 可见,Appium本身是借助于其他框架控制App
Selendroid的架构设计
从图中可以看出我们的Selendroid Server和我们在App在一个框内,那就意味这我们的Selendroid得跟我们的app在同一个进程内,我们的Selendroid才可以控制我们的App,如何把我们的Selendroid和App放在同一个进程内呢,需要我们在打包签名的的时候就把Selendroid Server打到包中,进而让安卓认为我们的Selendroid Server和我们的app是同一个程序,这样才可以控制我们的app,中间的Selendroid Standalone Driver控制的是app,Selendroid Standalone Driver所有的自动化指令来自于Http Server,Http Server所有的指令都是外部的WebDriver Client自动化指令,Selendroid Standalone Driver这个模块是基于android sdk基础上的一个模块,我们使用Selendroid Standalone Driver必须得有android sdk,为了测试混合的app的话必须的准备Appium,Test Device Test App,Appium-Python-Client,Selenium,区别在于Appium的配置上有区别。还有元素识别的工具有区别。
Hybrid使用的是:Chrome Inspector for Selendroid,原生的话使用:UiAutomator