pytest多線程與多設備并發(fā)appium
1、appium+python 實現(xiàn)單設備的 app 自動化測試
- 啟動 appium server,占用端口 4723
- 電腦與一個設備連接,通過 adb devices 獲取已連接的設備
- 在 python 代碼當中,編寫啟動參數(shù),通過 pytest 編寫測試用例,來進行自動化測試。
2、若要多設備并發(fā),同時執(zhí)行自動化測試,那么需要:
- 確定設備個數(shù)
- 每個設備對應一個 appium server 的端口號,并啟動 appium
- pytest 要獲取到每個設備的啟動參數(shù),然后執(zhí)行自動化測試。

3、實現(xiàn)策略
第一步:從設備池當中,獲取當前連接的設備。若設備池為空,則無設備連接。
第二步:若設備池不為空,啟動一個線程,用來啟動appium server.與設備個數(shù)對應。
起始server端口為4723,每多一個設備,端口號默認+4
第三步:若設備池不為空,則啟用多個線程,來執(zhí)行app自動化測試。
4、具體實現(xiàn)步驟
4.1 通過 adb 命令,獲取當前已連接的設備數(shù)、設備名稱、設備的安卓版本號。
定義一個 ManageDevices 類。
1. 重啟adb服務。
2. 通過adb devices命令獲取當前平臺中,已連接的設備個數(shù),和設備uuid.
3. 通過adb -P 5037 -s 設備uuid shell getprop ro.build.version.release獲取每一個設備的版本號。
4. 將所有已連接設備的設備名稱、設備版本號存儲在一個列表當中。
5. 通過調(diào)用get_devices_info函數(shù),即可獲得4中的列表。
實現(xiàn)的部分代碼為:
"""
@Title : app多設備并發(fā)-appium+pytest
@Author : 檸檬班-小簡
@Email : lemonban_simple@qq.com
"""
class ManageDevices:
"""
1、重啟adb服務。
2、通過adb devices命令獲取當前平臺中,已連接的設備個數(shù),和設備uuid.
3、通過adb -P 5037 -s 設備uuid shell getprop ro.build.version.release獲取每一個設備的版本號。
4、將所有已連接設備的設備名稱、設備版本號存儲在一個列表當中。
5、通過調(diào)用get_devices_info函數(shù),即可獲得4中的列表。
"""
def __init__(self):
self.__devices_info = []
# 重啟adb服務
self.__run_command_and_get_stout("adb kill-server")
self.__run_command_and_get_stout("adb start-server")
def get_devices_info(self):
"""
獲取已連接設備的uuid,和版本號。
:return: 所有已連接設備的uuid,和版本號。
"""
self.__get_devices_uuid()
print(self.__devices_info)
self.__get_device_platform_vesion()
return self.__devices_info4.2 定義一個設備配置池。
設備啟動參數(shù)管理池。
每一個設備:對應一個啟動參數(shù),以及appium服務的端口號。
1. desired_caps_config/desired_caps.yaml文件中存儲了啟動參數(shù)模板。
2. 從1中的模板讀取出啟動參數(shù)。
3. 從設備列表當中,獲取每個設備的設備uuid、版本號,與2中的啟動參數(shù)合并。
4. 每一個設備,指定一個appium服務端口號。從4723開始,每多一個設備,默認遞增4
5. 每一個設備,指定一個本地與設備tcp通信的端口號。從8200開始,每多一個設備,默認遞增4.
在啟動參數(shù)當中,通過systemPort指定。
因為appium服務會指定一個本地端口號,將數(shù)據(jù)轉(zhuǎn)發(fā)到安卓設備上。
默認都是使用8200端口,當有多個appium服務時就會出現(xiàn)端口沖突。會導致運行過程中出現(xiàn)socket hang up的報錯。
實現(xiàn)的部分代碼:
def devices_pool(port=4723,system_port=8200):
"""
設備啟動參數(shù)管理池。含啟動參數(shù)和對應的端口號
:param port: appium服務的端口號。每一個設備對應一個。
:param system_port: appium服務指定的本地端口,用來轉(zhuǎn)發(fā)數(shù)據(jù)給安卓設備。每一個設備對應一個。
:return: 所有已連接設備的啟動參數(shù)和appium端口號。
"""
desired_template = __get_yaml_data()
devs_pool = []
# 獲取當前連接的所有設備信息
m = ManageDevices()
all_devices_info = m.get_devices_info()
# 補充每一個設備的啟動信息,以及配置對應的appium server端口號
if all_devices_info:
for dev_info in all_devices_info:
dev_info.update(desired_template)
dev_info["systemPort"] = system_port
new_dict = {
"caps": dev_info,
"port": port
}
devs_pool.append(new_dict)
port += 4
system_port += 4
return devs_pool特別注意事項:2 個及 2 個以設備并發(fā)時,會遇到設備 socket hang up 的報錯。
原因是什么呢:
在 appium server 的日志當中,有這樣一行 adb 命令:adb -P 5037 -s 08e7c5997d2a forward tcp\:8200 tcp\:6790
什么意思呢?
將本地 8200 端口的數(shù)據(jù),轉(zhuǎn)發(fā)到安卓設備的 6790 端口
所以,本地啟動多個 appium server,都是用的 8200 端口,就會出現(xiàn)沖突。
解決方案:
應該設置為,每一個 appium server 用不同的本地端口號,去轉(zhuǎn)發(fā)數(shù)據(jù)給不同的設備。
啟動參數(shù)當中:添加systemPort= 端口號來設置。
這樣,每個設備都使用不同的本地端口,那么可解決此問題。
4.3 appium server 啟停管理 。
(ps 此處可以使用 appium 命令行版,也可以使用桌面版)
- 在自動化用例運行之前,必須讓 appium server 啟動起來。
- 在自動化用例執(zhí)行完成之后,要 kill 掉 appium 服務。這樣才不會影響下一次運行。
代碼實現(xiàn)如下:
import subprocess
import os
from Common.handle_path import appium_logs_dir
class ManageAppiumServer:
"""
appium desktop通過命令行啟動appium服務。
不同平臺上安裝的appium,默認的appium服務路徑不一樣。
初始化時,設置appium服務啟動路徑
再根據(jù)給定的端口號啟動appium
"""
def __init__(self,appium_server_apth):
self.server_apth = appium_server_apth
# 啟動appium server服務
def start_appium_server(self,port=4723):
appium_log_path = os.path.join(appium_logs_dir,"appium_server_{0}.log".format(port))
command = "node {0} -p {1} -g {2} " \
"--session-override " \
"--local-timezone " \
"--log-timestamp & ".format(self.server_apth, port, appium_log_path)
subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE,shell=True).communicate()
# 關閉appium服務
@classmethod
def stop_appium(cls,pc,post_num=4723):
'''關閉appium服務'''
if pc.upper() == 'WIN':
p = os.popen(f'netstat -aon|findstr {post_num}')
p0 = p.read().strip()
if p0 != '' and 'LISTENING' in p0:
p1 = int(p0.split('LISTENING')[1].strip()[0:4]) # 獲取進程號
os.popen(f'taskkill /F /PID {p1}') # 結束進程
print('appium server已結束')
elif pc.upper() == 'MAC':
p = os.popen(f'lsof -i tcp:{post_num}')
p0 = p.read()
if p0.strip() != '':
p1 = int(p0.split('\n')[1].split()[1]) # 獲取進程號
os.popen(f'kill {p1}') # 結束進程
print('appium server已結束')4.4 pytest 當中根據(jù)不同的啟動參數(shù)來執(zhí)行自動化測試用例
在使用 pytest 執(zhí)行用例時,是通過 pytest.main()會自動收集所有的用例,并自動執(zhí)行生成結果。

這種情況下,appium 會話的啟動信息是在代碼當中給定的。


以上模式當中,只會讀取一個設備的啟動信息,并啟動與設備的會話。
雖然 fixture 有參數(shù)可以傳遞多個設備啟動信息,但它是串行執(zhí)行的。
需要解決的問題的是:
- 可以傳遞多個設備的啟動參數(shù),但不是通過 fixture 的參數(shù)。
- 每傳遞一個設備啟動參數(shù)進來,執(zhí)行一次 pytest.main()
解決方案:
- 通過 pytest 的命令行參數(shù)。即在 pytest.main()的參數(shù)當中,將設備的啟動信息傳進來。
- 使用 python 的多線程來實現(xiàn)。每接收到一個設備啟動參數(shù),就啟動一個線程來執(zhí)行 pytest.main
4.4.1 第一個,pytest 的命令行參數(shù)。
首先需要在 conftest.py 添加命令行選項,命令行傳入?yún)?shù)”--cmdopt“。
用例如果需要用到從命令行傳入的參數(shù),就調(diào)用 cmdopt 函數(shù)。

def pytest_addoption(parser):
parser.addoption(
"--cmdopt", action="store", default="{platformName:'Android',platformVersion:'5.1.1'}",
help="my devices info"
)
@pytest.fixture(scope="session")
def cmdopt(request):
return request.config.getoption("--cmdopt")
@pytest.fixture
def start_app(cmdopt):
device = eval(cmdopt)
print("開始與設備 {} 進行會話,并執(zhí)行測試用例 ?。?.format(device["caps"]["deviceName"]))
driver = start_appium_session(device)
yield driver
driver.close_app()
driver.quit()4.4.2 使用多線程實現(xiàn): 每接收到一個設備啟動參數(shù),就啟動一個線程來執(zhí)行 pytest.main
定義一個 main.py。
run_case 函數(shù)。
此方法主要是:接收設備啟動參數(shù),通過 pytest.main 去收集并執(zhí)行用例。
# 根據(jù)設備啟動信息,通過pytest.main來收集并執(zhí)行用例。
def run_cases(device):
"""
參數(shù):device為設備啟動參數(shù)。在pytest.main當中,傳遞給--cmdopt選項。
"""
print(["-s", "-v", "--cmdopt={}".format(device)])
reports_path = os.path.join(reports_dir,"test_result_{}_{}.html".format(device["caps"]["deviceName"], device["port"]))
pytest.main(["-s", "-v",
"--cmdopt={}".format(device),
"--html={}".format(reports_path)]
)每有一個設備,就啟動一個線程,執(zhí)行 run_cases 方法。
# 第一步:從設備池當中,獲取當前連接的設備。若設備池為空,則無設備連接。
devices = devices_pool()
# 第二步:若設備池不為空,啟動appium server.與設備個數(shù)對應。起始server端口為4723,每多一個設備,端口號默認+4
if devices and platform_name and appium_server_path:
# 創(chuàng)建線程池
T = ThreadPoolExecutor()
# 實例化appium服務管理類。
mas = ManageAppiumServer(appium_server_path)
for device in devices:
# kill 端口,以免占用
mas.stop_appium(platform_name,device["port"])
# 啟動appium server
task = T.submit(mas.start_appium_server,device["port"])
time.sleep(1)
# 第三步:若設備池不為空,在appium server啟動的情況下,執(zhí)行app自動化測試。
time.sleep(15)
obj_list = []
for device in devices:
index = devices.index(device)
task = T.submit(run_cases,device)
obj_list.append(task)
time.sleep(1)
# 等待自動化任務執(zhí)行完成
for future in as_completed(obj_list):
data = future.result()
print(f"sub_thread: {data}")
# kill 掉appium server服務,釋放端口。
for device in devices:
ManageAppiumServer.stop_appium(platform_name, device["port"])到此這篇關于pytest多線程與多設備并發(fā)appium的文章就介紹到這了。希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
python使用pycharm環(huán)境調(diào)用opencv庫
這篇文章主要介紹了python使用pycharm環(huán)境調(diào)用opencv庫,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02
selenium+python配置chrome瀏覽器的選項的實現(xiàn)
這篇文章主要介紹了selenium+python配置chrome瀏覽器的選項的實現(xiàn)。文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-03-03
anaconda升級sklearn版本的實現(xiàn)方法
這篇文章主要介紹了anaconda升級sklearn版本的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-02-02
pandas dataframe統(tǒng)計填充空值方式
這篇文章主要介紹了pandas dataframe統(tǒng)計填充空值方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
Pandas數(shù)據(jù)分析之groupby函數(shù)用法實例詳解
這篇文章主要為大家介紹了Pandas數(shù)據(jù)分析之groupby函數(shù)用法實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-10-10
python pandas輕松通過特定列的值多條件去篩選數(shù)據(jù)及contains方法的使用
這篇文章主要介紹了python pandas輕松通過特定列的值多條件去篩選數(shù)據(jù)及contains方法的使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-02-02
用ldap作為django后端用戶登錄驗證的實現(xiàn)
這篇文章主要介紹了用ldap作為django后端用戶登錄驗證的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-12-12
Python機器學習算法庫scikit-learn學習之決策樹實現(xiàn)方法詳解
這篇文章主要介紹了Python機器學習算法庫scikit-learn學習之決策樹實現(xiàn)方法,結合實例形式分析了決策樹算法的原理及使用sklearn庫實現(xiàn)決策樹的相關操作技巧,需要的朋友可以參考下2019-07-07

