pytest多線程與多設(shè)備并發(fā)appium
1、appium+python 實(shí)現(xiàn)單設(shè)備的 app 自動(dòng)化測(cè)試
- 啟動(dòng) appium server,占用端口 4723
- 電腦與一個(gè)設(shè)備連接,通過(guò) adb devices 獲取已連接的設(shè)備
- 在 python 代碼當(dāng)中,編寫(xiě)啟動(dòng)參數(shù),通過(guò) pytest 編寫(xiě)測(cè)試用例,來(lái)進(jìn)行自動(dòng)化測(cè)試。
2、若要多設(shè)備并發(fā),同時(shí)執(zhí)行自動(dòng)化測(cè)試,那么需要:
- 確定設(shè)備個(gè)數(shù)
- 每個(gè)設(shè)備對(duì)應(yīng)一個(gè) appium server 的端口號(hào),并啟動(dòng) appium
- pytest 要獲取到每個(gè)設(shè)備的啟動(dòng)參數(shù),然后執(zhí)行自動(dòng)化測(cè)試。
3、實(shí)現(xiàn)策略
第一步:從設(shè)備池當(dāng)中,獲取當(dāng)前連接的設(shè)備。若設(shè)備池為空,則無(wú)設(shè)備連接。
第二步:若設(shè)備池不為空,啟動(dòng)一個(gè)線程,用來(lái)啟動(dòng)appium server.與設(shè)備個(gè)數(shù)對(duì)應(yīng)。
起始server端口為4723,每多一個(gè)設(shè)備,端口號(hào)默認(rèn)+4
第三步:若設(shè)備池不為空,則啟用多個(gè)線程,來(lái)執(zhí)行app自動(dòng)化測(cè)試。
4、具體實(shí)現(xiàn)步驟
4.1 通過(guò) adb 命令,獲取當(dāng)前已連接的設(shè)備數(shù)、設(shè)備名稱(chēng)、設(shè)備的安卓版本號(hào)。
定義一個(gè) ManageDevices 類(lèi)。
1. 重啟adb服務(wù)。
2. 通過(guò)adb devices命令獲取當(dāng)前平臺(tái)中,已連接的設(shè)備個(gè)數(shù),和設(shè)備uuid.
3. 通過(guò)adb -P 5037 -s 設(shè)備uuid shell getprop ro.build.version.release獲取每一個(gè)設(shè)備的版本號(hào)。
4. 將所有已連接設(shè)備的設(shè)備名稱(chēng)、設(shè)備版本號(hào)存儲(chǔ)在一個(gè)列表當(dāng)中。
5. 通過(guò)調(diào)用get_devices_info函數(shù),即可獲得4中的列表。
實(shí)現(xiàn)的部分代碼為:
""" @Title : app多設(shè)備并發(fā)-appium+pytest @Author : 檸檬班-小簡(jiǎn) @Email : lemonban_simple@qq.com """ class ManageDevices: """ 1、重啟adb服務(wù)。 2、通過(guò)adb devices命令獲取當(dāng)前平臺(tái)中,已連接的設(shè)備個(gè)數(shù),和設(shè)備uuid. 3、通過(guò)adb -P 5037 -s 設(shè)備uuid shell getprop ro.build.version.release獲取每一個(gè)設(shè)備的版本號(hào)。 4、將所有已連接設(shè)備的設(shè)備名稱(chēng)、設(shè)備版本號(hào)存儲(chǔ)在一個(gè)列表當(dāng)中。 5、通過(guò)調(diào)用get_devices_info函數(shù),即可獲得4中的列表。 """ def __init__(self): self.__devices_info = [] # 重啟adb服務(wù) self.__run_command_and_get_stout("adb kill-server") self.__run_command_and_get_stout("adb start-server") def get_devices_info(self): """ 獲取已連接設(shè)備的uuid,和版本號(hào)。 :return: 所有已連接設(shè)備的uuid,和版本號(hào)。 """ self.__get_devices_uuid() print(self.__devices_info) self.__get_device_platform_vesion() return self.__devices_info
4.2 定義一個(gè)設(shè)備配置池。
設(shè)備啟動(dòng)參數(shù)管理池。
每一個(gè)設(shè)備:對(duì)應(yīng)一個(gè)啟動(dòng)參數(shù),以及appium服務(wù)的端口號(hào)。
1. desired_caps_config/desired_caps.yaml文件中存儲(chǔ)了啟動(dòng)參數(shù)模板。
2. 從1中的模板讀取出啟動(dòng)參數(shù)。
3. 從設(shè)備列表當(dāng)中,獲取每個(gè)設(shè)備的設(shè)備uuid、版本號(hào),與2中的啟動(dòng)參數(shù)合并。
4. 每一個(gè)設(shè)備,指定一個(gè)appium服務(wù)端口號(hào)。從4723開(kāi)始,每多一個(gè)設(shè)備,默認(rèn)遞增4
5. 每一個(gè)設(shè)備,指定一個(gè)本地與設(shè)備tcp通信的端口號(hào)。從8200開(kāi)始,每多一個(gè)設(shè)備,默認(rèn)遞增4.
在啟動(dòng)參數(shù)當(dāng)中,通過(guò)systemPort指定。
因?yàn)閍ppium服務(wù)會(huì)指定一個(gè)本地端口號(hào),將數(shù)據(jù)轉(zhuǎn)發(fā)到安卓設(shè)備上。
默認(rèn)都是使用8200端口,當(dāng)有多個(gè)appium服務(wù)時(shí)就會(huì)出現(xiàn)端口沖突。會(huì)導(dǎo)致運(yùn)行過(guò)程中出現(xiàn)socket hang up的報(bào)錯(cuò)。
實(shí)現(xiàn)的部分代碼:
def devices_pool(port=4723,system_port=8200): """ 設(shè)備啟動(dòng)參數(shù)管理池。含啟動(dòng)參數(shù)和對(duì)應(yīng)的端口號(hào) :param port: appium服務(wù)的端口號(hào)。每一個(gè)設(shè)備對(duì)應(yīng)一個(gè)。 :param system_port: appium服務(wù)指定的本地端口,用來(lái)轉(zhuǎn)發(fā)數(shù)據(jù)給安卓設(shè)備。每一個(gè)設(shè)備對(duì)應(yīng)一個(gè)。 :return: 所有已連接設(shè)備的啟動(dòng)參數(shù)和appium端口號(hào)。 """ desired_template = __get_yaml_data() devs_pool = [] # 獲取當(dāng)前連接的所有設(shè)備信息 m = ManageDevices() all_devices_info = m.get_devices_info() # 補(bǔ)充每一個(gè)設(shè)備的啟動(dòng)信息,以及配置對(duì)應(yīng)的appium server端口號(hào) 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
特別注意事項(xiàng):2 個(gè)及 2 個(gè)以設(shè)備并發(fā)時(shí),會(huì)遇到設(shè)備 socket hang up 的報(bào)錯(cuò)。
原因是什么呢:
在 appium server 的日志當(dāng)中,有這樣一行 adb 命令:adb -P 5037 -s 08e7c5997d2a forward tcp\:8200 tcp\:6790
什么意思呢?
將本地 8200 端口的數(shù)據(jù),轉(zhuǎn)發(fā)到安卓設(shè)備的 6790 端口
所以,本地啟動(dòng)多個(gè) appium server,都是用的 8200 端口,就會(huì)出現(xiàn)沖突。
解決方案:
應(yīng)該設(shè)置為,每一個(gè) appium server 用不同的本地端口號(hào),去轉(zhuǎn)發(fā)數(shù)據(jù)給不同的設(shè)備。
啟動(dòng)參數(shù)當(dāng)中:添加systemPort= 端口號(hào)來(lái)設(shè)置。
這樣,每個(gè)設(shè)備都使用不同的本地端口,那么可解決此問(wèn)題。
4.3 appium server 啟停管理 。
(ps 此處可以使用 appium 命令行版,也可以使用桌面版)
- 在自動(dòng)化用例運(yùn)行之前,必須讓 appium server 啟動(dòng)起來(lái)。
- 在自動(dòng)化用例執(zhí)行完成之后,要 kill 掉 appium 服務(wù)。這樣才不會(huì)影響下一次運(yùn)行。
代碼實(shí)現(xiàn)如下:
import subprocess import os from Common.handle_path import appium_logs_dir class ManageAppiumServer: """ appium desktop通過(guò)命令行啟動(dòng)appium服務(wù)。 不同平臺(tái)上安裝的appium,默認(rèn)的appium服務(wù)路徑不一樣。 初始化時(shí),設(shè)置appium服務(wù)啟動(dòng)路徑 再根據(jù)給定的端口號(hào)啟動(dòng)appium """ def __init__(self,appium_server_apth): self.server_apth = appium_server_apth # 啟動(dòng)appium server服務(wù) 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() # 關(guān)閉appium服務(wù) @classmethod def stop_appium(cls,pc,post_num=4723): '''關(guān)閉appium服務(wù)''' 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]) # 獲取進(jìn)程號(hào) os.popen(f'taskkill /F /PID {p1}') # 結(jié)束進(jìn)程 print('appium server已結(jié)束') 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]) # 獲取進(jìn)程號(hào) os.popen(f'kill {p1}') # 結(jié)束進(jìn)程 print('appium server已結(jié)束')
4.4 pytest 當(dāng)中根據(jù)不同的啟動(dòng)參數(shù)來(lái)執(zhí)行自動(dòng)化測(cè)試用例
在使用 pytest 執(zhí)行用例時(shí),是通過(guò) pytest.main()會(huì)自動(dòng)收集所有的用例,并自動(dòng)執(zhí)行生成結(jié)果。
這種情況下,appium 會(huì)話的啟動(dòng)信息是在代碼當(dāng)中給定的。
以上模式當(dāng)中,只會(huì)讀取一個(gè)設(shè)備的啟動(dòng)信息,并啟動(dòng)與設(shè)備的會(huì)話。
雖然 fixture 有參數(shù)可以傳遞多個(gè)設(shè)備啟動(dòng)信息,但它是串行執(zhí)行的。
需要解決的問(wèn)題的是:
- 可以傳遞多個(gè)設(shè)備的啟動(dòng)參數(shù),但不是通過(guò) fixture 的參數(shù)。
- 每傳遞一個(gè)設(shè)備啟動(dòng)參數(shù)進(jìn)來(lái),執(zhí)行一次 pytest.main()
解決方案:
- 通過(guò) pytest 的命令行參數(shù)。即在 pytest.main()的參數(shù)當(dāng)中,將設(shè)備的啟動(dòng)信息傳進(jìn)來(lái)。
- 使用 python 的多線程來(lái)實(shí)現(xiàn)。每接收到一個(gè)設(shè)備啟動(dòng)參數(shù),就啟動(dòng)一個(gè)線程來(lái)執(zhí)行 pytest.main
4.4.1 第一個(gè),pytest 的命令行參數(shù)。
首先需要在 conftest.py 添加命令行選項(xiàng),命令行傳入?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("開(kāi)始與設(shè)備 {} 進(jìn)行會(huì)話,并執(zhí)行測(cè)試用例 ??!".format(device["caps"]["deviceName"])) driver = start_appium_session(device) yield driver driver.close_app() driver.quit()
4.4.2 使用多線程實(shí)現(xiàn): 每接收到一個(gè)設(shè)備啟動(dòng)參數(shù),就啟動(dòng)一個(gè)線程來(lái)執(zhí)行 pytest.main
定義一個(gè) main.py。
run_case 函數(shù)。
此方法主要是:接收設(shè)備啟動(dòng)參數(shù),通過(guò) pytest.main 去收集并執(zhí)行用例。
# 根據(jù)設(shè)備啟動(dòng)信息,通過(guò)pytest.main來(lái)收集并執(zhí)行用例。 def run_cases(device): """ 參數(shù):device為設(shè)備啟動(dòng)參數(shù)。在pytest.main當(dāng)中,傳遞給--cmdopt選項(xiàng)。 """ 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)] )
每有一個(gè)設(shè)備,就啟動(dòng)一個(gè)線程,執(zhí)行 run_cases 方法。
# 第一步:從設(shè)備池當(dāng)中,獲取當(dāng)前連接的設(shè)備。若設(shè)備池為空,則無(wú)設(shè)備連接。 devices = devices_pool() # 第二步:若設(shè)備池不為空,啟動(dòng)appium server.與設(shè)備個(gè)數(shù)對(duì)應(yīng)。起始server端口為4723,每多一個(gè)設(shè)備,端口號(hào)默認(rèn)+4 if devices and platform_name and appium_server_path: # 創(chuàng)建線程池 T = ThreadPoolExecutor() # 實(shí)例化appium服務(wù)管理類(lèi)。 mas = ManageAppiumServer(appium_server_path) for device in devices: # kill 端口,以免占用 mas.stop_appium(platform_name,device["port"]) # 啟動(dòng)appium server task = T.submit(mas.start_appium_server,device["port"]) time.sleep(1) # 第三步:若設(shè)備池不為空,在appium server啟動(dòng)的情況下,執(zhí)行app自動(dòng)化測(cè)試。 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) # 等待自動(dòng)化任務(wù)執(zhí)行完成 for future in as_completed(obj_list): data = future.result() print(f"sub_thread: {data}") # kill 掉appium server服務(wù),釋放端口。 for device in devices: ManageAppiumServer.stop_appium(platform_name, device["port"])
到此這篇關(guān)于pytest多線程與多設(shè)備并發(fā)appium的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
python使用pycharm環(huán)境調(diào)用opencv庫(kù)
這篇文章主要介紹了python使用pycharm環(huán)境調(diào)用opencv庫(kù),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02Python標(biāo)準(zhǔn)庫(kù)與第三方庫(kù)詳解
這篇文章主要介紹了Python標(biāo)準(zhǔn)庫(kù)與第三方庫(kù),需要的朋友可以參考下2014-07-07selenium+python配置chrome瀏覽器的選項(xiàng)的實(shí)現(xiàn)
這篇文章主要介紹了selenium+python配置chrome瀏覽器的選項(xiàng)的實(shí)現(xiàn)。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-03-03anaconda升級(jí)sklearn版本的實(shí)現(xiàn)方法
這篇文章主要介紹了anaconda升級(jí)sklearn版本的實(shí)現(xiàn)方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-02-02pandas dataframe統(tǒng)計(jì)填充空值方式
這篇文章主要介紹了pandas dataframe統(tǒng)計(jì)填充空值方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02Pandas數(shù)據(jù)分析之groupby函數(shù)用法實(shí)例詳解
這篇文章主要為大家介紹了Pandas數(shù)據(jù)分析之groupby函數(shù)用法實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10python pandas輕松通過(guò)特定列的值多條件去篩選數(shù)據(jù)及contains方法的使用
這篇文章主要介紹了python pandas輕松通過(guò)特定列的值多條件去篩選數(shù)據(jù)及contains方法的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-02-02用ldap作為django后端用戶(hù)登錄驗(yàn)證的實(shí)現(xiàn)
這篇文章主要介紹了用ldap作為django后端用戶(hù)登錄驗(yàn)證的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12Python機(jī)器學(xué)習(xí)算法庫(kù)scikit-learn學(xué)習(xí)之決策樹(shù)實(shí)現(xiàn)方法詳解
這篇文章主要介紹了Python機(jī)器學(xué)習(xí)算法庫(kù)scikit-learn學(xué)習(xí)之決策樹(shù)實(shí)現(xiàn)方法,結(jié)合實(shí)例形式分析了決策樹(shù)算法的原理及使用sklearn庫(kù)實(shí)現(xiàn)決策樹(shù)的相關(guān)操作技巧,需要的朋友可以參考下2019-07-07