Python+appium+unittest UI自動化測試資料

什麼是UI自動化

自動化分層
  1. 單元自動化測試,指對軟件中最小可測試單元進行檢查和驗證,通常須要藉助單元測試框架,如java的JUnit,python的unittest等
  2. 接口自動化測試,主要檢查驗證模塊間的調用返回以及不一樣系統、服務間的數據交換,常見的接口測試工具備postman、jmeter、loadrunner等;
  3. UI自動化測試,UI層是用戶使用產品的入口,全部功能經過這一層提供給用戶,測試工做大多集中在這一層,常見的測試工具備UFT、Robot Framework、Selenium、Appium等;

大部分公司只要求到第二層,及接口自動化測試,主要由於UI層常常改動,代碼可維護性弱,且若是需求常常變動時,對代碼邏輯也要常常改動。 但若是對於一些需求較爲穩定,測試重複性工做多的使用UI自動化則能大量減小人力物力在一些簡單的手動重複工做上。html

具體UI自動化實現

編程語言的選擇

python,是一門可讀性很強,很容易上手的編程語言,對於測試來講,能夠在短期內學會,並開始寫一下小程序。並且相對其Java來講,python能夠用20行代碼完成Java100行代碼的功能,而且避免了重複造輪子。java

自動化測試工具的選擇

appium,是一個開源的自動化測試工具,支持android、ios、mobile web、混合模式開發。在selenium的基礎上增長了對手機客戶端的特定操做,例如手勢操做和屏幕指向。node

測試框架的選擇

unittest,是python的單元測試框架,使用unittest能夠在有多個用例一塊兒執行時,一個用例執行失敗,其餘用例還能繼續執行。 且unittest引入了不少斷言,則測試過程當中十分方便去判讀測試用例的執行失敗與否。python

PageObject,是一種設計模式,通常使用在selenium自動化測試中。經過對頁面元素、操做的封裝,使得在後期對代碼的維護減小了不少冗餘工做。android

代碼框架

框架中主要是兩大塊,分別是result和testset,result用來存放執行用例後的html報告和日誌以及失敗時的截圖。ios

result 中主要以日期爲文件夾,裏面文件爲每次執行用例的測試報告及日誌,以及image文件夾,保存用例執行失敗時的截圖。web

TestRunner

首先介紹testRunner,這是整個系統的運行的開始。shell

# -*- coding: utf-8 -*- import threading import unittest from testSet.testcase.test_flight import Test_flight as testcase1 from testSet.testcase.test_test import test as testcase import testSet.common.report as report import testSet.page.basePage as basePage from testSet.common.myServer import myServer import time from testSet.common.log import logger import testSet.util.date as date createReport = report.report() # 建立測試報告 class runTest(): def __init__(self): pass def run(self, config, device): time.sleep(8) basePage.setconfig(config, device) # 將設備號和端口號傳給basepage suite = unittest.TestLoader().loadTestsFromTestCase(testcase1) # 將testcase1中的測試用例加入到測試集中 runner = createReport.getReportConfig() runner.run(suite) # 開始執行測試集 ms.quit() # 退出appium服務 def getDriver(self, driver): return driver class myThread(threading.Thread): def __init__(self, device, config): threading.Thread.__init__(self) self.device = device self.config = config def run(self): if __name__ == '__main__': test = runTest() test.run(self.config, self.device) # test.driverquit() createReport.getfp().close() # 關閉測試報告文件 log = logger(date.today_report_path).getlog() log.info(self.device + "test over") if __name__ == '__main__': try: devices = ["192.168.20.254:5555"] theading_pool = [] for device in devices: # 根據已鏈接的設備數,啓動多個線程 ms = myServer(device) config = ms.run() t = myThread(device, config) theading_pool.append(t) for t in theading_pool: t.start() time.sleep(5) for t in theading_pool: t.join() except: print("線程運行失敗") raise

testRunner包括runTest和myThead兩個類,myThead負責建立線程,runTest在線程中執行測試用例。編程

common

不具體說每一個文件的做用及代碼了,舉例兩個比較重要的。bootstrap

myServer

爲了能夠實現多設備並行測試,不能手動啓動appium客戶端後在執行用例,這樣只有1個設備分配到了appium的端口,也只能執行1個設備。所以須要用代碼實現啓動appium服務,併爲不一樣的設備分配不一樣的端口。

import os import unittest from time import sleep from .driver import driver from selenium.common.exceptions import WebDriverException import subprocess import time import urllib.request, urllib.error, urllib.parse import random import socket from .log import logger import testSet.util.date as date # 啓動appium class myServer(object): def __init__(self, device): # self.appiumPath = "D:\Appium" self.appiumPath = "F:\\Appium" self.device = device self.log = logger(date.today_report_path).getlog() def isOpen(self, ip, port): # 判斷端口是否被佔用 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect((ip, int(port))) s.shutdown(2) # shutdown參數表示後續能否讀寫 # print '%d is ok' % port return True except Exception as e: return False def getport(self): # 得到端口號 port = random.randint(4700, 4900) # 判斷端口是否被佔用 while self.isOpen('127.0.0.1', port): port = random.randint(4700, 4900) return port def run(self): """ 啓動appium服務 :return: aport 端口號 """ aport = self.getport() bport = self.getport() self.log.info("--------appium server start----------") # startCMD = "node D:\\Appium\\node_modules\\appium\\bin\\appium.js" # startCMD = "node Appium\\node_modules\\appium\\bin\\appium.js" cmd = 'appium' + ' -p ' + str(aport) + ' --bootstrap-port ' + str(bport) + ' -U ' + str(self.device) + " --session-override" rootDirection = self.appiumPath[:2] # 得到appium服務所在的磁盤位置 # 啓動appium # os.system(rootDirection + "&" + "cd" + self.appiumPath + "&" + startCMD) try: subprocess.Popen(rootDirection + "&" + "cd" + self.appiumPath + "&" + cmd, shell=True) # 啓動appium服務 return aport except Exception as msg: self.log.error(msg) raise def quit(self): """ 退出appium服務 :return: """ os.system('taskkill /f /im node.exe') self.log.info("----------------appium close---------------------")
driver

driver 負責鏈接手機,並啓動測試app

# -*- coding: utf-8 -*- from appium import webdriver from .log import logger from . import report import os import testSet.util.date as date import appium from selenium.common.exceptions import WebDriverException dr = webdriver.Remote class driver(object): def __init__(self, device): self.device = device self.desired_caps ={} self.desired_caps['platformName'] = 'Android' self.desired_caps['platformVersion'] = '5.0.2' self.desired_caps['udid'] = self.device self.desired_caps['deviceName'] = 'hermes' self.desired_caps['noReset'] = True self.desired_caps['appPackage'] = 'com.igola.travel' self.desired_caps['appActivity'] = 'com.igola.travel.ui.LaunchActivity' self.log = logger(date.today_report_path).getlog() def connect(self, port): url = 'http://localhost:%s/wd/hub' % str(port) self.log.debug(url) try: global dr dr = webdriver.Remote(url, self.desired_caps) self.log.debug("啓動接口爲:%s,手機ID爲:%s" % (str(port), self.device)) except Exception: self.log.info("appium 啓動失敗") os.popen("taskkill /f /im adb.exe") raise def getDriver(self): return dr
report

使用htmlTestRunner生成html測試報告

# -*- coding: utf-8 -*- import HTMLTestRunner import time import os import testSet.util.date as date class report: def __init__(self): self.runner = "" self.fp = "" self.sendReport() def sendReport(self): now = time.strftime("%Y-%m-%d-%H_%M_%S", time.localtime(time.time())) if not os.path.isdir(date.today_report_path): os.mkdir(date.today_report_path) report_abspath = os.path.join(date.today_report_path, now + '_report.html') self.fp = open(report_abspath, 'wb') self.runner = HTMLTestRunner.HTMLTestRunner( stream=self.fp, title="appium自動化測試報告", description="用例執行結果:") def getReportConfig(self): return self.runner def getfp(self): return self.fp
測試用例

實現機票預訂流程

from ddt import ddt, data, unpack import testSet.util.excel as excel from . import testcase import unittest from testSet.common.sreenshot import screenshot from testSet.page.homePage import homePage from testSet.page.flightPage import FlightPage from testSet.page.timelinePage import TimelinePage from testSet.page.summaryPage import SummaryPage from testSet.page.bookingPage import BookingPage from testSet.page.bookingDetailPage import BookingDetailPage from testSet.page.paymentPage import PaymentPage from testSet.page.orderDetailPage import OrderDetailPage from testSet.page.orderListPage import OrderListPage Excel = excel.Excel("flight", "Sheet1") isinit = False @ddt class Test_flight(testcase.Testcase): def setUp(self): super().setUp() self.cabin = ""  @screenshot def step01_go_flightpage(self, expected_result): """ 跳轉到找飛機頁面 """ homePage().go_flightPage()  @screenshot def step02_search(self, expected_result): """搜索跳轉 """ flight = FlightPage() self.assertTrue(flight.verify_page(), "找機票頁面進入錯誤") flight.select_ways(expected_result["type"]) flight.select_cabin(expected_result["cabin"]) FlightPage().search()  @screenshot def step03_timeline(self, expected_result): """ 驗證timeline的航程詳情是否正確 """ timeline = TimelinePage() for type in range(0, int(expected_result["type"])): self.assertTrue(timeline.verify_page(), "timeline頁面進入錯誤") # actual_result = timeline.get_flight_info() # self.assertDictContainsSubset(actual_result, expected_result, "航程詳情錯誤") timeline.select_flight(expected_result["price"])  @screenshot def step04_summary(self, expected_result): """ 驗證summary頁面的航程詳情是否正確 :return: """ summary = SummaryPage() self.assertTrue(summary.verify_page(), "summary頁面進入錯誤") summary.collapse() actual_result = summary.get_flight_info(expected_result["type"]) trips = [] for trip in expected_result.keys(): if "trip_type" in trip: trips.append(expected_result[trip]) leg_cabin = summary.check_cabin(expected_result["type"], *trips) if isinstance(leg_cabin, tuple): for key in leg_cabin[1].keys(): actual_result[key] = leg_cabin[1][key] self.cabin = leg_cabin[0] else: self.cabin = leg_cabin self.assertDictContainsSubset(actual_result, expected_result, "航程詳情錯誤") summary.collapse() summary.select_ota()  @screenshot def step05_go_booking(self, expected_result): """ 驗證booking航程詳情是否正確 """ booking = BookingPage() self.assertTrue(booking.verify_page(), "booking頁面進入錯誤") self.assertEqual(self.cabin, booking.check_cabin()) booking.go_detail()  @screenshot def step06_booking_detail(self, expected_result): booking_detail = BookingDetailPage() self.assertTrue(booking_detail.verify_page(), "booking航程詳情頁面進入錯誤") actual_result = booking_detail.get_flight_info(expected_result["type"]) self.assertDictContainsSubset(actual_result, expected_result, "航程詳情錯誤") booking_detail.back_to_booking()  @screenshot def step07_submit(self, expected_result): booking = BookingPage() booking.submit_order()  @screenshot def step08_payment(self, expected_result): payment = PaymentPage() self.assertTrue(payment.verify_page(), "payment 頁面進入錯誤") self.assertEqual(self.cabin, payment.check_cabin()) payment.pay_later()  @screenshot def step09_go_order(self, expected_result): homePage().go_order() self.assertTrue(OrderListPage().verify_page(), "訂單列表頁面進入錯誤") OrderListPage().go_order_detail()  @screenshot def step10_check_order(self, expected_result): detail = OrderDetailPage() self.assertTrue(detail.verify_page(), "訂單詳情頁面進入錯誤") detail.collapse() actual_result = detail.get_flight_info(expected_result["type"]) self.assertDictContainsSubset(actual_result, expected_result, "航程詳情錯誤") def _steps(self): for name in sorted(dir(self)): if name.startswith("step"): yield name, getattr(self, name)  @data(*Excel.next()) def test_flights_detail(self, data): for name, step in self._steps(): step(data)

Test_filght使用ddt,以數據爲驅動,在excel中保存各類測試數據,一行測試數據則爲一條用例,這樣能夠避免測試用例的冗餘,畢竟相似登陸就要測試各類狀況。在測試用例中將每一個步驟單獨分紅一個函數,在函數先後都要使用斷言判斷是否執行成功,若是不成功則後面的步驟都不執行,直接跳出開始嚇一條用例的執行。

測試步驟中有數字0一、02等,是爲了肯定測試的步驟順序,

def _steps(self): for name in sorted(dir(self)): if name.startswith("step"): yield name, getattr(self, name)

這一步則用來對測試用例中全部的以step開頭的函數進行排序,但須要注意的是,若是步驟超過10個,須要在1前面加上0,成爲01,由於在函數名中,數字是以string格式保存的,排序時,會先比較第一位,再比較第二位,這樣執行順序可能會變成1,10,11...,2,3。全部須要在1前面加上0,這樣執行順序就正確了。

在測試用例中只顯示每一步操做,具體判斷邏輯代碼都交由每一個頁面的具體實現代碼完成,即page文件夾中的文件。

而page文件也只執行相關業務判斷邏輯模塊,具體調用driver的手勢操做,例如click,swipe等,則交給basepage完成,page繼承於basepage。

相關文章
相關標籤/搜索