android自動化測試的實踐

功能性測試:

  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

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章