android自动化测试的实践

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012070360/article/details/81939402

功能性测试:

  1. App启动过程中的耗时情况
  2. CPU占比率
  3. 流量消耗情况
  4. 电量消耗情况
  5. 内存消耗情况
  6. 流畅度(FPS,就是每秒钟的帧数,流畅度,流畅度通过该指标就可以看到app流畅度异常的情况)
  7. 过度渲染(流畅度一个方面就是过度渲染)

环境的配置

1:android sdk:

这个需要解压,解压完了以后把相应的环境变量添加进去,添加两三个,

  1. 第一个是sdk的路径,
  2. platform tools 这个是为了我们待会要使用的adb 命令
  3. tools,这个是为了待会为了获取到界面元素工具:uiautomatorviewer

校验环境变量是否配置成功通过:adb devices,看有没有设备信息输出

emulator-5554    device

2:python2.7

去python的官网下载(https://www.python.org/downloads/),有两个版本3.5.2和2.7,建议使用2.7,这个两个差异很大甚至都不兼容。

3:pycharm

这个是脚本执行的编辑器,因为都是属于jetbrains,旗下产品Intellij IDEA、PyCharm、WebStorm,android studio,任何一个激活码在哪个平台都可以用,使用之前需要获取激活码破解(https://www.python.org/downloads/)

功能性测试:

启动时间

  • 冷启动:进程第一次被启动所消耗的时间的过程。启动指令: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个问题,所以我们使用框架来帮我们解决

应用测试框架的意义

  • 提高代码的易读性
  • 提高代码的效率
  • 提高代码的易维护性
  1. 自动化实例
  2. Unittest
  3. 数据驱动(大批量数据,降低冗余率)
  4. 实践

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安装成功了,如何使用呢

  1. 首先在类前面加个修饰,说明本次的测试用例使用的是ddt测试框架
    @ddt
    class LoginCase(MyTestCase):
  2. 在测试用例的方法前加上,这里的还分为有一个参数,和多个参数,如何是一个参数,只需要在@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

猜你喜欢

转载自blog.csdn.net/u012070360/article/details/81939402