移動端測試的八大過程
安裝/卸載
- 真機上安裝、卸載、高版本覆蓋安裝、低版本覆蓋安裝、卸載后安裝高版本;
- 安裝關注點:版本號、渠道號、數字簽名(用抓包工具輔助查看)、安裝成功后啟動向導、安裝過程中對意外情況的處理(取消、死機、重啟、斷電、內存不足、斷網)、安裝進度條、主要功能流程;
- 卸載關注點:卸載過程中的意外情況處理(取消、死機、重啟、斷電、內存不足、斷網)、卸載進度條;
- 第三方軟件協助安裝、卸載、高版本覆蓋安裝、低版本覆蓋安裝、卸載后安裝高版本;
- 在線升級:
- 升級注意點:升級提示、取消更新/強制更新、后台更新(ios的自動更新)、跨版本升級、升級過程中異常情況的處理(取消、死機、重啟、斷電、內存不足、斷網)、升級進度、不同網絡下升級;
- 第三方軟件支持:itools、豌豆莢、91助手、華為助手、360、應用寶等;
業務功能測試
- 根據需求文檔、原型圖和設計稿驗證app各個功能的實現;
- 共性功能:
- 注冊:用戶名密碼的輸入(同文本框編輯“2-1)”)、用戶名密碼長度限制、注冊后的頁面提示(手機短信提示)、前台和后台數據一致、;
- 登錄:用戶名密碼的輸入(同登錄“1-1)”)、非法登錄次數限制、多設備登錄(MTOP現有原則,一個應用同時只允許一台設備登錄)、禁用賬號登錄、登錄成功信息、登錄后有注銷按鈕、登錄超時處理、登錄過程斷網處理、登錄過程切換網絡;
- 注銷:注銷后新賬號登錄、取消注銷;
- 應用前后台切換:app前后台切換、鎖屏解屏、電話中斷后回到app、必須處理的提示框處理后回到app、殺掉進程后重新啟動app、有數據交換的頁面注意進行前后台切換以及鎖屏解屏;
- 免登錄:登錄后殺掉進程重新啟動app、無網絡、切換用戶登錄、密碼更換、主動退出登錄下次啟動app、卸載重裝、在線更新、覆蓋安裝、跨版本安裝、;
- 數據更新:手動或自動刷新、從后台切換到前台時數據更新、實時更新、定時更新、數據展示的處理邏輯(服務器獲取、本地緩存)、更新異常處理(弱網、斷網、服務器響應異常、數據為空);
- 定位、相機、語音、藍牙等服務:已開啟、未開啟根據提示開啟、未開啟並拒絕開啟;
- 時間測試:修改手機時區;
- 推送測試:推送消息內容、推送消息鏈接跳轉、免打擾或拒絕接收;
- 交叉事件測試
兼容測試
- 分辨率;
- 主流分辨率:1080*1920、720*1280、800*1280、2560*1440、 2040x1080等
- 非主流:1080*1800等
- 主流系統版本 ios:ios10、ios9、ios8、ios7;android:android6.0、android5.0、android4.*等;
- 不同廠家定制 iphone、華為、小米、oppo、vivo、魅族等
- 不同尺寸:6寸、5寸、5.5寸、5.7寸、4.7寸、4寸等
穩定性測試
- monkey結合友盟持續使用8小時以上統計crash率;
- 各種事件打擾,如插拔數據線、電話打擾、收發短信、切換網絡、瀏覽網絡、使用藍牙傳送/接收數據、相機等;
- 多個運行中app切換測試;
性能測試
- monkey結合性能測試工具監控cpu、內存、流量、耗電量,性能測試工具如anothermoniter、騰訊的GT;
- 評估典型用戶應用場景下,系統資源的使用情況;
- 大數據測試(如需要讀取用戶通訊錄的情況);
- 不同網絡響應速度、服務器接口壓力測試;
- 與競品的Benchmarking(基線測試);
網絡測試
- 無網絡測試;
- 弱網測試;
- 外網測試;
界面易用性測試
- 符合android或ios體驗規范;
- android體驗規范:長按彈出刪除選項(一時想不起來還有哪些,平時還是要多做總結);
- ios體驗規范:左滑彈出刪除選項、左右滑動可翻頁
- 符合用戶體驗規范:
- 是否有空數據界面設計,引導用戶去執行操作。
- 是否濫用用戶引導。
- 是否有不可點擊的效果,如:你的按鈕此時處於不可用狀態,那么一定要灰掉,或者拿掉按鈕,否則會給用戶誤導
- 菜單層次是否太深;
- 交互流程分支是否太多;
- 相關的選項是否離得很遠;
- 界面中按鈕可點擊范圍是否適中;
- 當切換標簽的時候,內容跟着切換;
- 是否定義Back的邏輯。涉及軟硬件交互時,Back鍵應具體定義
- 是否有橫屏模式的設計,應用一般需要支持橫屏模式,即自適應設計;
- 在不同的頁面是否有導航連接、導航與頁面風格一致;
- 是否需要搜索;
- 圖片質量、同一頁面圖片顏色不宜過多、同一頁面標簽風格統一;
- 文案:輸入框中說明文字、頁面文字正確性、敏感詞匯、敏感圖片(設計專利、版權、隱私等);
安全測試
- 軟件權限:
- 限制/允許使用手機功能接人互聯網
- 限制/允許使用手機發送接受信息功能
- 限制/允許應用程序來注冊自動啟動應用程序
- 限制或使用本地連接
- 限制/允許使用手機拍照或錄音
- 限制/允許使用手機讀取用戶數據
- 限制/允許使用手機寫人用戶數據
- 安裝/卸載安全性:
- 能夠在安裝設備驅動程序上找到應用程序的相應圖標
- 安裝路徑應能指定
- 沒有用戶的允許, 應用程序不能預先設定自動啟動
- 卸載是否安全, 其安裝進去的文件是否全部卸載
- 卸載用戶使用過程中產生的文件是否有提示
- 其修改的配置信息是否復原
- 卸載是否影響其他軟件的功能
- 卸載應該移除所有的文件
- 數據安全性:
- 輸人的密碼將不以明文形式進行顯示
- 密碼, 信用卡明細, 或其他的敏感數據將不被儲存在它們預輸人的位置上
- 不同的應用程序的密碼長度必需至少在4一8 個數字長度之間
- 當應用程序處理信用卡明細, 或其他的敏感數據時, 不以明文形式將數據寫到其它單獨的文件或者臨時文件中。以防止應用程序異常終止而又沒有側除它的臨時文件, 文件可能遭受人侵者的襲擊, 然后讀取這些數據信息。
- 當將敏感數據輸人到應用程序時, 其不會被儲存在設備中
- 備份應該加密, 恢復數據應考慮恢復過程的異常,通訊中斷等, 數據恢復后再使用前應該經過校驗
- 應用程序應考慮系統或者虛擬機器產生的用戶提示信息或安全警告
- 應用程序不能忽略系統或者虛擬機器產生的用戶提示信息或安全警告, 更不能在安全警告顯示前,利用顯示誤導信息欺騙用戶,應用程序不應該模擬進行安全警告誤導用戶
- 在數據刪除之前,應用程序應當通知用戶或者應用程序提供一個“取消”命令的操作
- “ 取消”命令操作能夠按照設計要求實現其功能
- 應用程序應當能夠處理當不允許應用軟件連接到個人信息管理的情況
- 當進行讀或寫用戶信息操作時, 應用程序將會向用戶發送一個操作錯誤的提示信息
- 在沒有用戶明確許可的前提下不損壞刪除個人信息管理應用程序中的任何內容
- 應用程序讀和寫數據正確。
- 應用程序應當有異常保護。
- 如果數據庫中重要的數據正要被重寫, 應及時告知用戶
- 能合理地處理出現的錯誤
- 意外情況下應提示用戶
Appium兩種連接方式
- 連接真機
- 連接夜神模擬器→DOS窗口輸入:adb connect 127.0.0.1:62001 在輸入adb devices命令
相關環境的搭建
JDK的配置
Genymotion的下載與安裝
Appium自動化測試環境搭建
ADB的常用命令
- ADB全名Android Debug Brage,是一個Debug工具,adb是一個標准的C/S結構的工具,是要連接開發電腦和調試手機的.
- adb幫助 adb --help
- 啟動adb server adb start-server
- 關閉adb server adb kill-server
- 獲取設備號 adb devices
- 獲取系統版本 adb -s 設備號 shell getprop ro.build.version.release
- 發送文件到手機 adb push 電腦端文件路徑/需要發送的文件 手機端存儲的路徑 例:將手機/sdcard目錄中的xx.png文件,發送到電腦桌面 adb push c:\users\win\Desktop /sdcard/xx.png
- 從手機拉取文件 adb pull 手機端的路徑/拉取文件名 電腦端存儲文件路徑 adb pull /sdcard/xx.png c:\Users\win\Desktop
- 查看手機運行日志 adb logcat
- 手機shell命令行 adb shell
- 獲取app包名和啟動名 在windows終端運行 adb shell dumpsys window | findstr mCurrentFocus 類似於com.android的內容是包名,類似於.settings的內容是啟動名
- 安裝app到手機 adb install 路徑/xx.apk
- 卸載手機app adb uninstall 包名
- 獲取app啟動時間 adb shell am start -w 包名/啟動名
- thisTime 該activity啟動耗時
- totaltime 應用自身啟動耗時=thistime+應用application等資源啟動時間
- waittime 系統啟動應用耗時=totaltime+系統資源啟動時間
Appium(android與ios皆可測)
- 使用python打開android模擬器中的設置界面
- 思路:python代碼→Appium-python庫→Appium→手機
- 獲取app包名和啟動名 current_package print(driver.current_package)
- 獲取啟動名 current_activity print(driver.current_activity)
- 腳本內啟動其他app driver.start_activity(appPackage,appActivity) 如果一個腳本啟動多個app,那么會按順序依次打開程序
- appPackage:包名
- appActivity:啟動名
- 關閉app driver.close_app() 關閉當前操作的app,不會關閉驅動對象
- 關閉驅動對象 driver.quit() 關閉驅動對象,同時關閉所有關聯的app
- 安裝apk到手機 driver.install_app(app_path) app_path:腳本機器中apk的文件路徑
- 手機中移出app driver.remove_app(app_id) app_id:需要卸載的app包名
- 判斷app是否安裝 driver.is_app_installed(app_id) app_id:app包名,返回True表示已安裝,false表示未安裝
- 發送文件到手機
- 需要先引入import base64
- data=str(base64.b64encode(data.encode('utf-8')),'utf-8') driver.push_file(path,data)
- path:表示手機設備上的路徑
- data:文件內數據,要求base64編碼
- 從手機中拉取文件
- 先引入import base64
- data=driver.pull_file(path) 返回數據為base64編碼 參數path為手機設備上的路徑
- print(str(base64.b64decode(data),'utf-8')) base64解碼
- 獲取當前屏幕內元素結構 driver.page_source 作用:返回當前頁面的文檔結構,判斷特定的元素是否存在
- 應用於后台事件,app放置后台,模擬熱啟動 方法:background_app(seconds) seconds表示停留在后台的時間,單位:秒 driver.background_app(seconds) ,app置於后台5s后,再次展示當前頁面
模板
from appium import webdriver
import time
desired_caps = {}
desired_caps['platformName'] = 'Android' #android的apk還是IOS的ipa
desired_caps['platformVersion'] = '7.0' #android系統的版本號
desired_caps['deviceName'] = '192.168.190.101:5555' #手機設備名稱,通過adb devices 查看
desired_caps['appPackage'] = 'com.android.email' #apk的包名
desired_caps['appActivity'] = '.activity.setup.AccountSetupFinal' #apk的launcherActivity
# desired_caps['unicodeKeyboard'] = True # 使用unicodeKeyboard的編碼方式來發送字符串
# desired_caps['resetKeyboard'] = True # # 將鍵盤給隱藏起來
driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)
UIAutomatorViewer工具(元素定位工具)
用來掃描和分析Android的應用程序的UI控件的工具,如果tools中沒有,打開monitor.bat。
操作工具
- 進入SDK的tools目錄下,找到UIAutomatorViewer.bat並打開
- 電腦連接真機或android模擬器
- 連接元素定位工具
- 注意:如果出現報錯有如下幾種解決方式:
- app服務進程占用,可以暫時關閉adb服務(然后在開啟adb服務) adb kill-server(關閉) adb start-server(開啟)
- 另一種情況就是使用python編寫自動化腳本並執行階段,如果頁面時腳本執行出來的頁面,在連接元素定位工具的時候同樣會報錯(解決辦法就是關閉執行腳本出來的頁面,重新打開即可,親測有效)
- 簡單暴力的方法,就是拔掉數據線重新連接(親測有效)
- 會涉及到多次截取頁面,所以可以使用頁面截取與保存文件,方便快速使用(我使用的monitor.bat我沒找到保存的子樣)
元素定位API
前置代碼
from appium import webdriver
import time
desired_caps = {
'platformName': 'Android',
'deviceName': '7cd2ae65',
'platformVersion': '9',
'appPackage': 'com.tencent.mm',
'appActivity': '.ui.LauncherUI',
'automationName': 'Appium',
# 運行模擬器中文的問題需要加如下兩行
# 'unicodeKeyboard':True,
# 'resetKeyboard':True,
'noReset': True,
'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'}
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
通過id定位
需要注意跟css不一樣,這里的id值並不是完全唯一的
- driver.find.element_by_id("com.tencent.mm:id/s6").click() 元素定位工具中resource-id 對應 com.tencent.mm:id/s6 這里定位單個元素
- driver.find.elements_by_id(" ").click()
通過class定位
- driver.find.element_by_class_name("android.widget.ImageButton").click() 元素定位工具中class 對應 android.widget.ImageButton
通過xpath定位
- 常用的屬性定位(id、text、class等固定的情況下)
- driver.find_element_by_xpath("//*[@text='掃一掃']").click() 通過xpath使用text去定位
- driver.find_element_by_xpath("//*[@resource-id='com.taobao.taobao:id/tv_scan_text']").click() 通過xpath使用id去定位
- driver.find_element_by_xpath("//*[@class='android.widget.EditText']").click() 通過xpath使用class去定位
- driver.find_element_by_xpath("//*[@resource-id='com.taobao.taobao:id/tv_scan_text'][@text='掃一掃']").click() 通過xpath使用多種方式進行定位
- driver.find_element_by_xpath("//*[@content-desc='幫助']").click() 通過xpath使用content-desc進行定位
- contains模糊定位(同樣可以模糊id與class)
- 模糊定位的訴求 對於一個元素的id或者text不是唯一的,但有一部分是唯一的,這種就可以模糊匹配。
- 具體代碼實現 driver.find_element_by_xpath("//*[contains(@content-desc, '幫助')]").click() 通過xpath使用contains進行模糊定位
- 組合定位
- 組合定位的訴求 如果一個元素有2個屬性,通過xpath也可以同時匹配2個屬性,text, resource-id,class ,index,content-desc這些屬性都能任意組合定位
- 具體的實現
- driver.find_element_by_xpath('//android.widget.EditText[@resource-id="com.taobao.taobao:id/home_searchedit"]').click() id和class組合
- driver.find_element_by_xpath('//*[@text="注冊/登錄" and @index="1"]').click() text和index組合
- driver.find_element_by_xpath('//android.widget.EditText[@text="請輸入手機號碼"]').send_keys("512200893") class和text組合
- driver.find_element_by_xpath('//*[contains(@resource-id, "aliuser_menu_item_help") and @content-desc="幫助"]').click() id和content-desc組合
- 層級定位
- 層級定位的訴求 如果一個元素,它除了class屬性(class屬性肯定會有),其它屬性啥都沒有,這種情況用上面方法就不適用了,這個時候可以找他父元素,通過父親定位兒子
- 通過父元素定位子元素的具體的實現
- driver.find_element_by_xpath('//*[@resoure-id="com.taobao.taobao:id/home_searchbar"]/android.widget.EditText').click() 父元素下其中一個子元素
- driver.find_element_by_xpath('//*[@resource-id="com.taobao.taobao:id/ll_navigation_tab_layout"]/android.widget.FrameLayout[2]').click() 父元素下,有多個相同class的兒子時候,可以通過xpath的索引去取對應第幾個,xpath是從1開始數的
- 通過子元素定位父元素的具體的實現
- driver.find_element_by_xpath('//*[@resource-id="com.taobao.taobao:id/tv_scan_text"]/..').click()
- driver.find_element_by_xpath('//*[@resource-id="com.taobao.taobao:id/tv_scan_text"]/parent::*').click()
WebDriverWait顯示等待
在一個超時時間范圍內,每隔一段時間去搜索一次元素是否存在,如果存在返回定位對象,如果不存在直到超時時間到達,報超時異常錯誤。
方法:WebDriverWait(driver, timeout, poll_frequency).until(method)
參數:
- driver:手機驅動對象
- timeout:搜索超時時間
- poll_frequency:每次搜索間隔時間,默認時間為0.5s
- method:定位方法(匿名函數)
匿名函數
lambda x:x
等價於python函數:
def test(x):
return x
使用用例:
from selenium.webdriver.support.wait import WebDriverWait(需要導入)
WebDriverWait(driver,10).until(lambda x:x.find_element_by_id('elementid'))
解釋:
- x傳入值為:driver,所以才可以使用定位方法
- 搜索到元素后until返回定位對象,沒有搜索到until返回超時異常錯誤.
元素操作API
- 點擊元素 click()
- 發送數據到輸入框 send_key()
- 解決輸入中文的問題(這種情況一般在模擬器中會出現,真機暫時未出現)
- 'unicodeKeyboard':True
- 'resetKeyboard':True
- 解決輸入中文的問題(這種情況一般在模擬器中會出現,真機暫時未出現)
- 清空輸入框的內容 clear()
- 獲取元素的文本內容 text
- 獲取元素的屬性值 get_attribute(value)
- value值為name時,返回content-desc/text屬性值
- value值為text時,返回text屬性值
- value值為classname,返回class屬性值
- value值為resourceId,返回resource-id屬性值
滑動和拖拽事件
真機或者模擬機獲取坐標,打開開發者選項點擊指針模式
swipe滑動事件
從一個坐標位置滑動到另一個坐標位置,只能是兩個點之間的滑動
方法:swipe(start_x,start_y,end_x,end_y,duration=None)
參數:
- start_x:起點x軸坐標
- start_y:起點y軸坐標
- end_x:終點x軸坐標
- end_y:終點y軸坐標
- duration:滑動這個操作一共持續的時間長度,單位:ms
相關代碼
driver.swipe(100,2000,100,1000,3000) 時間參數設置越長越精確,一般設置為2-3,3秒最佳
scroll滑動事件
從一個元素滑動到另一個元素,直到頁面自動停止
方法:scroll(origin_el,destination_el)
參數:
- origin_el 滑動開始的元素
- destination_el 滑動結束的元素
相關代碼:
save_button=driver.find_element_by_xpath("//*[contains(@text,'存儲')]")
more_button=driver.find_element_by_xpath("//*[contains(@text,'存儲')]")
driver.scroll(save_button,more_button)
drag滑動事件
和scroll的事件類似
方法drag_and_drop(origin_el,destination_el)
參數:
- origin_el 滑動開始的元素
- destination_el 滑動結束的元素
相關代碼:
save_button=driver.find_element_by_xpath("//*[contains(@text,'存儲')]")
more_button=driver.find_element_by_xpath("//*[contains(@text,'存儲')]")
driver.drag_and_drop(save_button,more_button)
三種方式的區別:
- scroll和drag的區別在於,drag沒有慣性,相同點:都是使用元素進行傳參
- 對於swipe傳的是元素,主要是以坐標為主
滑動事件進行強化使用
使用循環對滑動相關代碼進行優化:
while True:
try:
driver.find_element_by_xpath("//*[contains(@text,'位置信息')]").click() # 如果在滑動期間找到了這個元素的位置信息,那么就點擊
break
except Exception:
driver.swipe(100, 2000, 100, 1000, 5000) # 如果在try中沒有找到的時候,那么就會列入循環繼續尋找,直到找到之后,才會停止, 如果一直進入死循環,意味着元素不存在
滑動事件使用獲取手機寬高增強使用
while True:
try:
driver.find_element_by_xpath("//*[contains(@text,'位置信息')]").click() # 如果在滑動期間找到了位置信息這個元素,那么就點擊
break
except Exception:
window_width=driver.get_window_size()["width"]
window_height=driver.get_window_size()["height"]
start_x=window_width*0.5
end_x=start_x
end_y=window_height*0.25
start_y=end_y*3
driver.swipe(start_x,start_y,end_x,end_y,5000) # 如果在try中沒有找到的時候,那么就會列入循環繼續尋找,直到找到之后,才會停止,如果一直進入死循環,意味着元素不存在
滑動事件適應寬高最終版
def getSize(self):
x = self.driver.get_window_size()['width']
y = self.driver.get_window_size()['height']
return (x, y)
def swipeUp(self,t):
l = self.getSize()
x1 = int(l[0] * 0.5) # x坐標
y1 = int(l[1] * 0.75) # 起始y坐標
y2 = int(l[1] * 0.25) # 終點y坐標
self.driver.swipe(x1, y1, x1, y2, t)
額外內容(python測試代碼優化)
# 獲取關於手機里面中,指定一個文本內容是5.1的
for i in eles:
if i.text == "5.1": # 或者使用 if "5.1" in i.text:
print("有!")
break
else:
print("沒有")
高級手勢TouchAction
TouchAction是AppiumDriver的輔助類,主要針對手勢操作,比如滑動、長按、拖動等,原理是將一系列的動作放在一個鏈條中發送到服務器,服務器接受到該鏈條后,解析各個動作,逐個執行。
注意的是,所有的手勢都要通過執行perform()函數才會運行。
手指輕敲操作
模擬手指輕敲一下屏幕操作
方法:tap(element=None,x=None,y=None)
方法:perform() # 發送命令到服務器執行操作
參數:
- element:被定位到的元素
- x:相對於元素左上角的坐標,通常會使用元素的x軸坐標
- y:通常會使用元素的y軸坐標
相關代碼:
more_button=driver.find_element_by_xpath("//*[contains(@text,'更多')]")
# 輕敲
# 只傳入元素,就會點擊元素
TouchAction(driver).tap(more_button).perform()
# 只傳入右邊兩個參數值x與y,會點擊以屏幕左上角為原點,點擊
TouchAction(driver).tap(x=850,y=1500).perform()
# 元素和xy都傳,以元素為准
TouchAction(driver).tap(more_button,600,600).perform()
手指按下和抬起操作
方法:press(el=None,x=None,y=None)
方法:release() # 結束動作,手指離開屏幕
參數:
- element:被定位到的元素
- x:通常會使用元素的x軸坐標
- y:通常會使用元素的y軸坐標
代碼實現:
el=driver.find_element_by_xpath("//*[contains(@text,'WLAN')]")
# 通過元素定位
TouchAction(driver).press(el),release().perform()
# 通過坐標方式按下屏幕,WLAN坐標:x=155,y=250
TouchAction(driver).tap(x=155,y=250).release().perform()
等待操作只能進行長按元素☆☆☆☆☆
TouchAction(driver).press(el).wait(5000).perform()
強行使用該方法組合去進行長按操作,不能完成,但是不會報錯
手指長按操作
用途:類似於長按wifi取消保存這種功能
方法:long_press(el=None,x=None,y=None,duration=1000)
參數:
- element:被定為到的元素
- x:通常會使用元素的x軸坐標
- y:通常會使用元素的y軸坐標
- duration:持續時間,默認為1000ms
相關代碼:
el=driver.find_element_by_id("android:id/title")
#通過元素定位方式長按元素
TouchAction(driver).long_press(el,duration=5000).release().perform()
#同樣可以通過坐標進行定位
TouchAction(driver).long_press(x=600,y=600,duration=5000).release().perform()
手指移動操作
方法:move_to(el=None,x=None,y=None)
參數:
- el:定位的元素
- x:相對於前一個元素的x軸偏移量☆☆☆☆☆
- y:相對於前一個元素的y軸偏移量☆☆☆☆☆
相關代碼實現:
save_button=friver.find_element_by_xpath("//*[contains(@text,'存儲')]")
more_button=friver.find_element_by_xpath("//*[contains(@text,'更多')]")
# 元素方式滑動
TouchAction(driver).press(save_button).move_to(more_button).release().perform()
# 坐標方式滑動
TouchAction(driver).press(x=240,y=600).wait(100).move_to(x=100,y=100).release().perform()
手機操作API
- 獲取手機時間 print(device_time)
- 獲取手機的寬高 print(driver.get_window_size())
- 獲取手機的寬 print(driver.get_window_size()['width'])
發送鍵到設備
使用場景:音量鍵的增加與減少
方法:keyevent(keycode,metastate=None)
方法:press_keycode(keycode,metastate=None)
參數
- keycode:發送給設備的關鍵代碼(關鍵代碼可以百度Android keycode)
- metastate:關於被發送的關鍵代碼的元信息,一般為默認值
代碼實現:
for i in range(3):
driver.keyevent(24)
操作手機通知欄
方法:open_notifications()
代碼實現:
driver.open_notifications()
獲取手機當前網絡
方法:network_connection
代碼實現:
print(driver.open_notifications())
手機截圖
方法:get_screenshot_as_file(filename)
參數:filename表示指定路徑下,指定格式的圖片
代碼實現:
driver.get_screenshot_as_file("./xxx.png")
po模式(可以模糊的認為是封裝)
page Object Model 測試頁面和測試腳本分離,即頁面封裝成類,供測試腳本進行調用
優點:
- 提高測試用例的可讀性
- 減少了代碼的重復
- 提高測試用例的可維護性,特別是針對UI頻繁變動的項目
缺點:結構復雜,基於流程做了模塊化分析的拆分
封裝前置代碼
先將前置代碼封裝,代碼如下:
from appium import webdriver
def init_driver():
desired_caps = {
'platformName': 'Android',
'deviceName': '7cd2ae65',
'platformVersion': '9',
# apk包名
'appPackage': 'com.tencent.mm',
# apk的launcherActivity
'appActivity': '.ui.LauncherUI',
'automationName': 'Appium',
'noReset': True,
'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'},
# 'unicodeKeyboard': True,
# # 屏蔽手機軟鍵盤
# 'resetKeyboard': True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
return driver其次,調用封裝的代碼以及注意事項,代碼如下:
# 在使用pytest命令的時候,python是不會自動檢索自己封裝的包(代碼)
# 所以需要引入如下的兩條,來強制性去檢索自己封裝的包
import os,sys
sys.path.append(os.getcwd())
from base.base_ini import init_driver
import time
import pytest
class TestAppoint:
def setup(self):
self.driver=init_driver()
time.sleep(2)
# 第一個參數使用元祖,第二個參數使用列表嵌套元祖
@pytest.mark.parametrize(("username","password"),[("wupeng","123456"),("wupeng1","123456")])
def test_search(self,username,password):
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys(username)
self.driver.find_element_by_id("android:id/search_src_text").send_keys(password)
分離測試腳本
編寫步驟
- 新建page文件夾
- 新建network_page.py文件
- 新建display_page.py文件
- init函數傳入driver
- init進入需要測試的頁面
- page中新建小動作函數
- 移動代碼
- 修改測試文件中的代碼
文件目錄
# po模式
- base
- - base_driver.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_display.py
- pytest.ini
base目錄的相關代碼 → base_driver.py
from appium import webdriver
def init_driver():
desired_caps = {
'platformName': 'Android',
'deviceName': '127.0.0.1:62001',
'platformVersion': '5.1.1',
# apk包名
'appPackage': 'com.android.settings',
# apk的launcherActivity
'appActivity': '.Settings',
'automationName': 'Appium',
'noReset': True,
# 'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'},
# 'unicodeKeyboard': True,
# # 屏蔽手機軟鍵盤
# 'resetKeyboard': True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
return driverpage目錄的相關代碼 → display_page.py
class DisPlayPage:
def __init__(self,driver):
self.driver=driver
def click_display(self):
# 實現點擊設置 點擊的內容放到page中
self.driver.find_element_by_xpath("//*[@text='設置']").click()
def click_search(self):
self.driver.find_element_by_id("com.android.settings:id/search").click()
def input_text(self,text):
self.driver.find_element_by_id("android:id/search_src_text").send_keys(text)
def click_return(self):
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
scripts目錄的相關代碼 → test_display.py
# 在使用pytest命令的時候,python是不會自動檢索自己封裝的包(代碼)
# 所以需要引入如下的兩條,來強制性去檢索自己封裝的包
import os,sys
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisPlayPage
import time
import pytest
class TestAppoint:
def setup(self):
self.driver=init_driver()
self.display_page=DisPlayPage(self.driver)
time.sleep(2)
def test_more(self):
# 執行點擊設置的按鈕
self.display_page.click_display()
time.sleep(2)
# 執行點擊搜索
self.display_page.click_search()
time.sleep(2)
# 執行搜索內容
self.display_page.input_text("hello")
# 執行返回
self.display_page.click_return()
抽取元素的特征
好處:若特征改了,流程不變,可以直接在上面修改
步驟:
- 將find_element_by_xxx改為find_element
- 將方式和具體特征向上移動
文件目錄
# po模式
- base
- - base_driver.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_display.py
- pytest.ini
使用代碼實例如下:
base_driver.py代碼
from appium import webdriver
def init_driver():
desired_caps = {
'platformName': 'Android',
'deviceName': '127.0.0.1:62001',
'platformVersion': '5.1.1',
# apk包名
'appPackage': 'com.android.settings',
# apk的launcherActivity
'appActivity': '.Settings',
'automationName': 'Appium',
'noReset': True,
# 'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'},
# 'unicodeKeyboard': True,
# # 屏蔽手機軟鍵盤
# 'resetKeyboard': True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
return driverdisplay_page.py代碼
from selenium.webdriver.common.by import By
class DisPlayPage:
display_click=By.XPATH,"//*[@text='設置']"
search_click=By.ID,"com.android.settings:id/search"
input_content=By.ID,"android:id/search_src_text"
return_click=By.CLASS_NAME,"android.widget.ImageButton"
def __init__(self,driver):
self.driver=driver
def click_display(self):
self.find_element(self.display_click).click()
def click_search(self):
self.find_element(self.search_click).click()
def input_text(self,text):
self.find_element(self.input_content).send_keys(text)
def click_return(self):
self.find_element(self.return_click).click()
# 起到輔助的作用,為了方便其他方法只需要傳入兩個參數
def find_element(self,loc):
return self.driver.find_element(loc[0],loc[1])test_display.py代碼
# 在使用pytest命令的時候,python是不會自動檢索自己封裝的包(代碼)
# 所以需要引入如下的兩條,來強制性去檢索自己封裝的包
import os,sys
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisPlayPage
import time
import pytest
class TestAppoint:
def setup(self):
self.driver=init_driver()
self.display_page=DisPlayPage(self.driver)
time.sleep(2)
def test_more(self):
# 執行點擊設置的按鈕
self.display_page.click_display()
time.sleep(2)
# 執行點擊搜索
self.display_page.click_search()
time.sleep(2)
# 執行搜索內容
self.display_page.input_text("hello")
# 執行返回
self.display_page.click_return()
PO模式的終結版本
使用封裝、分離、抽取元素特征、抽取action以及增肌顯示等待整合成最終po模式的移動端自動化測試腳本
文件目錄
# po模式
- base
- - base_driver.py
- - base_action.py
- page
- - display_page.py
- scripts
- - test_display.py
- pytest.ini
具體詳細的分解代碼如下:
base_driver.py代碼
from appium import webdriver
# 多用於封裝手機連接的前半部分
def init_driver():
desired_caps = {
'platformName': 'Android',
'deviceName': '127.0.0.1:62001',
'platformVersion': '5.1.1',
# apk包名
'appPackage': 'com.android.settings',
# apk的launcherActivity
'appActivity': '.Settings',
'automationName': 'Appium',
'noReset': True,
# 'chromeOptions': {'androidProcess': 'com.tencent.mm:appbrand0'},
# 'unicodeKeyboard': True,
# # 屏蔽手機軟鍵盤
# 'resetKeyboard': True
}
driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
return driverbase_action.py代碼
from appium.webdriver.common.touch_action import TouchAction
from selenium.webdriver.support.wait import WebDriverWait
class BaseAction:
def __init__(self, driver):
self.driver = driver
def click(self, loc):
self.find_element(loc).click()
def input_text(self, loc, text):
self.find_element(loc).send_keys(text)
# 實現Touch的perform的方法
def touchAction(self,loc):
TouchAction(self.driver).tap(self.find_element(loc)).perform()
def touchActionCoord(self,x,y):
TouchAction(self.driver).tap(x=x,y=y).perform()
def find_element(self, loc):
by = loc[0]
value = loc[1]
webdriverwarit = WebDriverWait(self.driver, 5, 1).until(lambda x: x.find_element(by, value))
return webdriverwaritdisplay_page.py代碼
from selenium.webdriver.common.by import By
from base.base_action import BaseAction
class DisPlayPage(BaseAction):
display_click=By.XPATH,"//*[@text='設置']"
search_click=By.ID,"com.android.settings:id/search"
input_content=By.ID,"android:id/search_src_text"
return_click=By.CLASS_NAME,"android.widget.ImageButton"
def __init__(self,driver):
BaseAction.__init__(self,driver)
self.click_display()
def click_display(self):
self.click(self.display_click)
def click_search(self):
self.click(self.search_click)
def input_search_text(self,text):
self.input_text(self.input_content,text)
def click_return(self):
self.click(self.return_click)test_display.py代碼
# 在使用pytest命令的時候,python是不會自動檢索自己封裝的包(代碼)
# 所以需要引入如下的兩條,來強制性去檢索自己封裝的包
import os,sys
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisPlayPage
import time
import pytest
class TestAppoint:
def setup(self):
self.driver=init_driver()
self.display_page=DisPlayPage(self.driver)
time.sleep(2)
def test_more(self):
# 執行點擊設置的按鈕
self.display_page.click_display()
time.sleep(2)
# 執行點擊搜索
self.display_page.click_search()
time.sleep(2)
# 執行搜索內容
self.display_page.input_search_text("hello")
# 執行返回
self.display_page.click_return()
Yaml的用法
Yaml數據存儲文件
YAML是一種所有編程語言可用的友好的數據序列化標准,語法和其他高階語言類似,並且可以簡單表達清單、散列
語法規則
- 大小寫敏感
- 使用縮進表示層級關系
- 縮進時不允許使用Tab鍵,只允許使用空格(在pycharm中可以使用tab)
- 縮進的空格數目不重要,只要相同層級的元素左側對齊即可
支持的數據結構
- 對象:鍵值對的集合,又稱為映射
- 數組:一組按次序排列的值,又稱為序列
- 純量:單個的、不可再分的值
yaml字典列表之間的相互嵌套
# 字典嵌套字典
name: "wupeng"
people:
age: "18"
name: "zhangsan"
# 字典嵌套列表
names: "wupeng1"
peoples:
- "1"
- "2"
# 列表嵌套列表
- "1"
- "2"
-
- "3"
- "4"
# 列表嵌套字典
- "1"
-
name: "wupeng"
age: "18"純量
包含:字符串、布爾值、整數、浮點數、Null、日期
# 字符串
name: "123"
# 布爾值
boolean: True
# 整數
number: 1
# 浮點數
number1: 3.1415
# Null值
null: None
# 日期,年月日,時分秒
date: 2018-09-10 05:00:23:111
Python解析yaml文件
PyYAML為python解析yaml的庫
安裝:pip3 install -U PyYAML
yaml的讀
import yaml
def main():
with open("./data.yml","r") as file:
data=yaml.load(file)
print(data)
if __name__ == '__main__':
main()yaml的寫
yaml.dump(data,stream,**kwds)
常用參數:
data:寫入數據類型為字典
stream:打開文件對象
encoding='utf-8' #設置寫入編碼方式
allow_unicode=True #是否允許unicode編碼
import yaml
def main():
data={"searchFile":{
"search1":{"name1":"wupeng","age1":18},
"search2":{"name2":"wulei","age2":20}
}}
with open("./text.yml","w") as file:
yaml.dump(data,file,encoding="uft-8",allow_unicode=True)
if __name__ == '__main__':
main()錨點和引用 &info為錨點,<<: *info表示引用 引用錨點的時候,會價格錨點后的所有都復制一份到引用的地方
name:
&info
value: "12"
value2: "sdf"
people:
name: "wulei"
<<: *info待續
字符串相關方法
a="20,40,50"
print(a.split(",")) 將字符串a分割成一個列表
刪除尾部的字符串 a.rstrip("80")
判斷字符串的類型 type(a)
Monkey
Monkey程序介紹
- Monkey程序由Androidixtong自帶,使用java語言編寫,在Android文件系統中的存放路徑是/system/framework/monkey.jar
- Monkey程序是由名為monkey的shell腳本來啟動執行,shell腳本在Android文件系統中的存放路徑是:/system/bin/monkey
- Monkey的啟動方式
- 可以通過PC機CMD窗口執行:adb shell monkey{+命令參數}來進行Monkey測試
- 在pc上adb shell進入Android系統,通過執行monkey{+命令參數}來進行Monkey測試
- 在Android機或者模擬器上直接執行monkey命令,可以在Android上安裝Android終端模擬器(Terminal Emulator for Android)
Monkey輸出日志
adb shell monkey -p cn.goapk.market 100>路徑/log.txt
Monkey基本參數介紹
- -p<允許的包名列表>
- 用此參數指定一個或多個包,指定包之后,monkey將只允許系統啟動指定的app,如果是指定包,monkey將允許系統啟動設備中的所有app
- 指定一個包 adb sell monkey -p cn.goapl.market 100
- 指定多個包 adb shell monkey -p fishjoy.control.menu -p cn.goapk.market 100
- -v
- 用於指定反饋信息級別(信息級別就是日志的詳細程度),總共分3個級別
- level 0: adb shell monkey -p cn.goapk.market -v 100 缺省值,僅提供啟動提示,測試完成和最終結果等少量信息
- level 1: adb shell monkey -p cn.goapk.market -v -v 100 提供較為詳細的日志,包括每個發送到Activity的時間信息
- level 2: adb shell monkey -p cn.goapk.market -v -v -v 100 最詳細的日志,包括了測試中選中/未選中的Activity信息
- -s 隨機數種子,用於指定偽隨機數生成器的seed值,如果seed相同,則兩次Monkey測試所產生的而時間序列也相同
- --throttle<毫秒> 用於用戶操作間的時延,單位是毫秒,如果指定這個參數,monkey會盡可能快的生成和發送消息 adb shell monkey -p cn.goapk.market --throttle 3000 100
Monkey的日志分析
- 正常情況 如果Monkey測試順利執行完成,在log的最后,會打印出當期那執行時間的次數和所花費的時間:monkey finished代表執行完成
- 異常情況 monkey測試出現錯誤后,一般的分析步驟看Monkey的日志(注意第一個swith以及異常信息等)
- 程序無響應的問題 在日志中搜索"ANR"
- 崩潰問題 在日志中搜索"Exception"(如果出現空指針,NullPointerException),肯定有bug
- Monkey執行中斷,在log最后也能看到當前執行次數
APP常規測試流程
- 測試資源准備
- 需求文檔
- 接口文檔
- 原型圖
- 主流機型
- 測試用例設計和評審
- 盡可能考慮多種情況
- 立項會議討論
- ui 和效果圖是否一致,如果不一致,以效果圖為准,協助兩邊溝通
- 功能 從邏輯的角度與需求文檔做對比
- 中斷測試
- 電話,短信,鬧鍾
- 前后台
- 兼容性
- andriod與ios以及相關機型
- 分辨率
- 系統版本
- 性能測試
- 啟動時間
- 電量消耗
- 流量
- 壓力測試 monkey
- 安全測試
- 傳輸數據是否是明文
- 密碼是不是md5
- 測試報告
- 自動化