前言:html
在迴歸測試階段,UI測試,兼容測試是測試的必要步驟。UI自動化的自己是比較冗餘的測試,可是換個角度思考,UI自動化同時鏈接多臺設備,那麼在迴歸測試時,在同一個腳本下產生的測試結果是很是有價值的。java
不一樣設備在併發下的測試結果能夠爲咱們提供:node
1. 兼容性測試(不一樣的手機品牌,Android版本, 分辨率等)python
2. 性能測試(經過安裝Emmagee,監控不一樣手機在同腳本下,性能的變化)web
3. 界面對比(經過圖像識別opencv,截圖對比等 查看在相同頁面的變化)shell
思路:windows
1. 啓動多路appium服務session
2. 啓動並鏈接多路手機端多線程
3. 運行並生成測試報告併發
問題:
1. python的unittest框架和java不一樣,不支持參數傳入,能夠經過重寫unittest.TestCase的init添加參數
2. appium 經過命令行啓動,須要安裝非desktop的版本,且最好安裝1.9版本appium,1.0我啓動不了
框架代碼截取:
1. 重寫unittest的初始化函數
class ParametrizedCase(unittest.TestCase): def __init__(self, methodName='runTest', param=None): super(ParametrizedCase, self).__init__(methodName) global devices devices = param @classmethod def setUpClass(cls): cls.driver = connect_device(devices) @classmethod def tearDownClass(cls): cls.driver.close_app() cls.driver.quit()
2. 封裝啓動appium的服務方法:
基於 appium 的啓動命令
appium -p -bp -U
封裝多線程啓動
class AppiumServer: def __init__(self, kwargs=None): self.kwargs = kwargs def start_server(self): """start the appium server """ for i in range(0, len(self.kwargs)): cmd = "appium --session-override -p %s -bp %s -U %s" % ( self.kwargs[i]["port"], self.kwargs[i]["bport"], self.kwargs[i]["devices"]) print(cmd) if platform.system() == "Windows": # windows下啓動server t1 = RunServer(cmd) p = Process(target=t1.start()) p.start() while True: print("--------start_win_server-------------") if self.win_is_runnnig("http://127.0.0.1:" + self.kwargs[i]["port"] + "/wd/hub" + "/status"): print("-------win_server_ 成功--------------") break else: appium = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=1, close_fds=True) while True: appium_line = appium.stdout.readline().strip().decode() time.sleep(1) print("---------start_server----------") if 'listener started' in appium_line or 'Error: listen' in appium_line: print("----server_ 成功---") break def win_is_runnnig(self, url): """Determine whether server is running :return:True or False """ response = None time.sleep(1) try: response = urllib.request.urlopen(url, timeout=5) if str(response.getcode()).startswith("2"): return True else: return False except URLError: return False except socket.timeout: return False finally: if response: response.close() def stop_server(self, devices): sysstr = platform.system() if sysstr == 'Windows': os.popen("taskkill /f /im node.exe") else: for device in devices: # mac cmd = "lsof -i :{0}".format(device["port"]) plist = os.popen(cmd).readlines() plisttmp = plist[1].split(" ") plists = plisttmp[1].split(" ") # print plists[0] os.popen("kill -9 {0}".format(plists[0])) class RunServer(threading.Thread): def __init__(self, cmd): threading.Thread.__init__(self) self.cmd = cmd def run(self): os.system(self.cmd)
3. 封裝鏈接Android設備的方法:
def connect_device(devices): desired_caps = {} desired_caps['platformVersion'] = devices["platformVersion"] desired_caps['platformName'] = devices["platformName"] desired_caps["automationName"] = devices['automationName'] desired_caps['deviceName'] = devices["deviceName"] desired_caps["appPackage"] = devices["appPackage"] desired_caps["appActivity"] = devices["appActivity"] desired_caps["noReset"] = True desired_caps['noSign'] = True desired_caps["unicodeKeyboard"] = True desired_caps["resetKeyboard"] = True desired_caps["systemPort"] = devices["systemPort"] # desired_caps['app'] = devices["app"] remote = "http://127.0.0.1:" + str(devices["port"]) + "/wd/hub" # remote = "http://127.0.0.1:" + "4723" + "/wd/hub" driver = webdriver.Remote(remote, desired_caps) return driver
4. 多線程啓動服務和多線程鏈接多終端,生成日誌報告
def runnerPool(getDevices): devices_Pool = [] for i in range(0, len(getDevices)): _initApp = {} _initApp["deviceName"] = getDevices[i]["devices"] _initApp["platformVersion"] = getPhoneInfo(devices=_initApp["deviceName"])["release"] _initApp["platformName"] = "Android" _initApp["port"] = getDevices[i]["port"] _initApp["automationName"] = "UiAutomator2" _initApp["systemPort"] = getDevices[i]["systemPort"] _initApp["appPackage"] = 'cn.vsx.vc' _initApp["appActivity"] = '.activity.RegistActivity' devices_Pool.append(_initApp) print(f'devices pool are {devices_Pool}') with ProcessPoolExecutor(len(devices_Pool)) as pool: pool.map(runnerCaseApp, devices_Pool) def runnerCaseApp(devices): suite = unittest.TestSuite() suite.addTest(ParametrizedCase.parametrize(group_call, param=devices)) # 加入測試類 now = time.strftime('%Y-%m-%d %H_%M_%S') result = BeautifulReport(suite) result.report(filename=now + f"_{devices['deviceName'].split(':', 1)[0]}.html", log_path='E:\\TestReports\\', description=f"{devices['deviceName'].split(':', 1)[0]}") if __name__ == '__main__': devices = attached_devices() if len(devices) > 0: l_devices = [] for dev in devices: app = {} app["devices"] = dev app["port"] = str(random.randint(4700, 4900)) app["bport"] = str(random.randint(4700, 4900)) app["systemPort"] = random.randint(4700, 4900) l_devices.append(app) print(f'list of server:{l_devices}') appium_server = AppiumServer(l_devices) appium_server.start_server() runnerPool(l_devices) appium_server.stop_server(l_devices) else: print("沒有可用的安卓設備")
以上爲大致的運行思路,只截取部分代碼,其餘的缺失代碼可自行思考
思路擴展:
1. unittest框架但願升級成pytest框架更靈活,支持的插件也更多。
2. allure報告支持pytest,更詳細美觀且可支持Jenkins持續集成
3. 可替換appium爲阿里的macaca
(全平臺支持,不限制移動端
更專業的 Node 模塊開發和封裝
驅動更加快速、穩定
本地到持續集成的方案提供
技術棧更新更快
協議更自由 MIT
全面的技術支持
在國內社區更有優點) 固然 appium也不錯,實在下載不了就考慮macaca
4. atx uiautomator2 的模式也能夠參考,優點在於可經過WiFi進行多手機併發