自動化分層
- 單元自動化測試,指對軟件中最小可測試單元進行檢查和驗證,通常須要藉助單元測試框架,如java的JUnit,python的unittest等
- 接口自動化測試,主要檢查驗證模塊間的調用返回以及不一樣系統、服務間的數據交換,常見的接口測試工具備postman、jmeter、loadrunner等;
- 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