android自動化測試的實踐
功能性測試:
- App啟動過程中的耗時情況
- CPU佔比率
- 流量消耗情況
- 電量消耗情況
- 記憶體消耗情況
- 流暢度(FPS,就是每秒鐘的幀數,流暢度,流暢度通過該指標就可以看到app流暢度異常的情況)
- 過度渲染(流暢度一個方面就是過度渲染)
環境的配置
- 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,這個兩個差異很大甚至都不相容。
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個問題,所以我們使用框架來幫我們解決
應用測試框架的意義
- 提高程式碼的易讀性
- 提高程式碼的效率
- 提高程式碼的易維護性
- 自動化例項
- 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